@lovek4d/kickflip 0.0.0-stage → 0.1.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 (111) hide show
  1. package/CONTEXT.md +414 -0
  2. package/LICENSE +57 -0
  3. package/README.md +23 -2
  4. package/SKILL.md +121 -0
  5. package/bin/doctor.mjs +3 -0
  6. package/bin/install.mjs +3 -0
  7. package/bin/kickflip.mjs +125 -0
  8. package/bin/statusline.mjs +4 -0
  9. package/bin/wait-events.mjs +3 -0
  10. package/bin/web-ts.mjs +2 -0
  11. package/board/bin/agent.mjs +2 -0
  12. package/board/bin/board.mjs +207 -0
  13. package/board/bin/builder-stop-hook.mjs +5 -0
  14. package/board/build-script.mjs +1 -0
  15. package/board/domain.mjs +3 -0
  16. package/board/public/app-asks.js +169 -0
  17. package/board/public/app-banner.js +48 -0
  18. package/board/public/app-cards.js +200 -0
  19. package/board/public/app-core.js +96 -0
  20. package/board/public/app-header.js +108 -0
  21. package/board/public/app-lanes.js +92 -0
  22. package/board/public/app-needs.js +30 -0
  23. package/board/public/app-snapshot.js +97 -0
  24. package/board/public/app-threads.js +165 -0
  25. package/board/public/app.html +83 -0
  26. package/board/public/app.js +327 -0
  27. package/board/public/attach.js +84 -0
  28. package/board/public/dialogs.js +360 -0
  29. package/board/public/hub-kit.js +151 -0
  30. package/board/public/hub.css +164 -0
  31. package/board/public/kickflip.png +0 -0
  32. package/board/public/prompts.js +64 -0
  33. package/board/public/prototype-kit.js +193 -0
  34. package/board/public/prototype-switcher.js +226 -0
  35. package/board/public/prototype.html +55 -0
  36. package/board/public/router.js +223 -0
  37. package/board/public/rules-editor.js +127 -0
  38. package/board/public/shell.js +298 -0
  39. package/board/public/sidebar.css +199 -0
  40. package/board/public/sidebar.js +287 -0
  41. package/board/public/style.css +570 -0
  42. package/board/public/toasts.js +179 -0
  43. package/board/public/ui-cards.js +153 -0
  44. package/board/public/ui-enums.js +10 -0
  45. package/board/public/ui-header.js +84 -0
  46. package/board/public/ui-hub.js +94 -0
  47. package/board/public/ui-links.js +142 -0
  48. package/board/public/ui-markdown.js +80 -0
  49. package/board/public/ui-page-header.js +63 -0
  50. package/board/public/ui-prompts.js +39 -0
  51. package/board/public/ui-questions.js +130 -0
  52. package/board/public/ui-sidebar.js +177 -0
  53. package/board/public/ui-snapshot.js +56 -0
  54. package/board/public/ui-threads.js +81 -0
  55. package/board/public/ui.js +235 -0
  56. package/board/public/view-frame.js +93 -0
  57. package/board/public/view-hub-settings.js +266 -0
  58. package/board/public/view-hub.js +99 -0
  59. package/board/public/view-notes.js +29 -0
  60. package/board/public/view-polling.js +87 -0
  61. package/board/public/view-questions.js +391 -0
  62. package/board/public/view-settings.js +328 -0
  63. package/board/server.mjs +12 -0
  64. package/board/store.mjs +7 -0
  65. package/board/tickets.mjs +3 -0
  66. package/board/worktree.mjs +14 -0
  67. package/docs/hub-session.md +11 -0
  68. package/hooks-plugin/.claude-plugin/plugin.json +5 -0
  69. package/hooks-plugin/hooks/codex-share-auto.mjs +21 -0
  70. package/hooks-plugin/hooks/hooks.json +54 -0
  71. package/hooks-plugin/hooks/usage-gate.mjs +18 -0
  72. package/hub/board-repo.mjs +1 -0
  73. package/hub/boards.mjs +4 -0
  74. package/hub/close-board.mjs +1 -0
  75. package/hub/files.mjs +1 -0
  76. package/hub/github.mjs +5 -0
  77. package/hub/home.mjs +1 -0
  78. package/hub/hub.mjs +17 -0
  79. package/hub/icon-180.png +0 -0
  80. package/hub/icon-192.png +0 -0
  81. package/hub/icon-512.png +0 -0
  82. package/hub/icons.mjs +1 -0
  83. package/hub/linear.mjs +2 -0
  84. package/hub/live.mjs +2 -0
  85. package/hub/locks.mjs +1 -0
  86. package/hub/new-board.mjs +2 -0
  87. package/hub/permission-hook.mjs +3 -0
  88. package/hub/presence.mjs +1 -0
  89. package/hub/prompts.mjs +2 -0
  90. package/hub/push.mjs +2 -0
  91. package/hub/repos.mjs +2 -0
  92. package/hub/routes.mjs +1 -0
  93. package/hub/rules.mjs +1 -0
  94. package/hub/schedule.mjs +1 -0
  95. package/hub/sessions.mjs +2 -0
  96. package/hub/shell.mjs +1 -0
  97. package/hub/stream.mjs +19 -0
  98. package/hub/sw.js +144 -0
  99. package/hub/try.mjs +3 -0
  100. package/hub/usage.mjs +3 -0
  101. package/hub/views.mjs +1 -0
  102. package/package.json +26 -4
  103. package/shared/codex.mjs +1 -0
  104. package/shared/colors.css +13 -0
  105. package/shared/repos.mjs +1 -0
  106. package/shared/service.mjs +33 -0
  107. package/shared/usage-gate.mjs +1 -0
  108. package/starter/cross-site.mjs +1 -0
  109. package/starter/public/claude.js +67 -0
  110. package/starter/public/index.html +49 -0
  111. package/starter/server.mjs +4 -0
@@ -0,0 +1,570 @@
1
+ * { box-sizing: border-box; }
2
+ html { -webkit-text-size-adjust: 100%; }
3
+ /* clip, not hidden: hidden makes body a scroll container and breaks the sticky header. */
4
+ body { overflow-x: clip; }
5
+ /* app-core.js steady scrolls the board after a repaint; the browser's own scroll anchoring would correct the same shift a second time. */
6
+ html:has(.view-board) { overflow-anchor: none; }
7
+ body { margin: 0; background: var(--bg); color: var(--text);
8
+ font: 14px/1.45 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif; }
9
+ a { color: var(--accent); text-decoration: none; }
10
+ a:hover { text-decoration: underline; }
11
+ button { font: inherit; color: inherit; cursor: pointer; border: 1px solid var(--line); background: var(--panel);
12
+ border-radius: 7px; padding: 6px 10px; }
13
+ button:disabled { opacity: .5; cursor: default; }
14
+ button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
15
+ a.button { display: inline-block; border: 1px solid var(--line); border-radius: 7px; padding: 6px 10px; background: var(--panel); color: var(--text); text-decoration: none; }
16
+ /* resolves: the button that clears a card's Action needed (board/domain.mjs resolvedBy), in that badge's notice tone. */
17
+ :is(a, button).resolves, button.build { border-color: var(--color-notice); background: var(--color-notice); color: #1c1407; }
18
+ button.ghost { border-color: transparent; background: transparent; font-size: 18px; padding: 2px 8px; }
19
+ textarea, input { font: inherit; color: inherit; background: var(--panel); border: 1px solid var(--line);
20
+ border-radius: 8px; padding: 8px 10px; width: 100%; resize: vertical; }
21
+ textarea:focus, input:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
22
+ .muted { color: var(--muted); }
23
+ .small { font-size: 12px; }
24
+ .row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 6px; }
25
+
26
+ .top { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; justify-content: space-between;
27
+ padding: max(10px, env(safe-area-inset-top)) 16px 10px; background: color-mix(in srgb, var(--bg) 88%, transparent);
28
+ backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); }
29
+ .brand { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
30
+ .brand :is(h1, h2) { font-size: 16px; margin: 0; }
31
+ .nav { display: flex; align-items: center; gap: 6px; }
32
+ .pill { border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; font-size: 12px; color: var(--text); }
33
+ .pill.hot { background: var(--accent); border-color: var(--accent); color: #fff; }
34
+
35
+ main { max-width: 860px; margin: 0 auto; padding: 12px 16px 80px; }
36
+
37
+ .waiting { background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
38
+ border-radius: 10px; padding: 10px 12px; margin-bottom: 12px; }
39
+ .waiting h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; margin: 0 0 6px; color: var(--accent); }
40
+ .waiting ul { margin: 0; padding-left: 18px; }
41
+ .waiting li { margin: 3px 0; }
42
+ .waiting li.prompt { list-style: none; margin: 6px 0 10px -18px; padding: 8px 10px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; }
43
+ .prompt .prompt-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
44
+ .prompt .prompt-head .small { overflow-wrap: anywhere; }
45
+ .prompt p { margin: 6px 0; }
46
+ .prompt .prompt-detail { margin: 6px 0; padding: 6px 8px; background: var(--chip); border-radius: 6px; font-size: 12.5px;
47
+ white-space: pre-wrap; overflow-wrap: anywhere; max-height: 10em; overflow: auto; }
48
+ .prompt .prompt-options { display: flex; flex-direction: column; gap: 6px; }
49
+ .prompt .prompt-options button { display: grid; gap: 2px; text-align: left; }
50
+ .prompt .prompt-options button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }
51
+ .prompt .prompt-options .lbl { font-weight: 600; }
52
+ .prompt .prompt-options .det { color: var(--muted); font-size: 13px; }
53
+ .prompt .prompt-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
54
+ .prompt button.deny { color: var(--color-danger); }
55
+ .prompt .prompt-rule { margin: 6px 0 0; }
56
+
57
+ .lane { margin-top: 14px; }
58
+ .lane .list { border: 1px solid var(--line); border-radius: 10px; background: var(--panel); overflow: hidden; }
59
+ .lane .empty { padding: 10px 12px; color: var(--muted); }
60
+
61
+ .item { border-top: 1px solid var(--line); }
62
+ .item:first-child { border-top: 0; }
63
+ .item > summary { list-style: none; cursor: pointer; display: grid; grid-template-columns: 18px 1fr auto; gap: 8px;
64
+ align-items: center; padding: 9px 12px; }
65
+ .item > summary::-webkit-details-marker { display: none; }
66
+ .item > summary:hover { background: color-mix(in srgb, var(--chip) 60%, transparent); }
67
+ .title-wrap { min-width: 0; }
68
+ .title { overflow-wrap: anywhere; }
69
+ .sub { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
70
+ .meta { display: flex; gap: 5px; align-items: center; flex-wrap: wrap; justify-content: flex-end; min-width: 0; }
71
+ .chip { display: inline-flex; align-items: center; gap: 4px; background: var(--chip); border-radius: 6px;
72
+ padding: 1px 7px; font-size: 12px; white-space: nowrap; color: var(--text); }
73
+ button.chip { border: 0; }
74
+ .chip.tone-quiet { color: var(--color-quiet); } .chip.tone-normal { color: var(--color-info); } .chip.tone-notice { color: var(--color-notice); }
75
+ .chip.tone-danger { color: var(--color-danger); } .chip.tone-ready { color: var(--color-success); }
76
+ /* Action needed keeps the notice orange; unread takes the accent so the two never read alike. */
77
+ .chip.unread { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
78
+ .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; background: var(--color-quiet); }
79
+ .dot.pass { background: var(--color-success); } .dot.pending { background: var(--color-notice); } .dot.fail { background: var(--color-danger); }
80
+
81
+ .status { width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--color-quiet); display: inline-block; position: relative; }
82
+ .status.progress { border-color: var(--color-notice); background: conic-gradient(var(--color-notice) 0 50%, transparent 50% 100%); }
83
+ .status.review { border-color: var(--color-success); background: conic-gradient(var(--color-success) 0 75%, transparent 75% 100%); }
84
+ .status.todo { border-style: dashed; }
85
+ .sub.agent { color: var(--color-notice); }
86
+ .sub.next { color: var(--color-info); }
87
+ .toast-stack { position: fixed; left: 50%; bottom: calc(var(--app-bottom, 0px) + 16px + env(safe-area-inset-bottom));
88
+ width: min(520px, calc(100vw - 32px)); height: 0; transform: translateX(-50%); z-index: 25; pointer-events: none; }
89
+ .toast-row { position: absolute; top: 0; left: 0; width: 100%; display: flex; justify-content: center;
90
+ transition: transform 200ms ease; pointer-events: none; }
91
+ .toast { background: var(--text); color: var(--bg); border-radius: 10px; padding: 8px 14px; font-size: 14px;
92
+ width: max-content; max-width: 100%; box-sizing: border-box; overflow-wrap: anywhere; cursor: pointer; pointer-events: auto;
93
+ box-shadow: 0 4px 16px rgba(0,0,0,.2); touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
94
+ .toast:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
95
+ @media (prefers-reduced-motion: reduce) { .toast-row { transition: none; } }
96
+ .status.done { border-color: var(--color-info); background: var(--color-info); }
97
+
98
+ .detail { padding: 4px 12px 14px 38px; }
99
+ .grid { display: grid; grid-template-columns: 110px 1fr; gap: 4px 10px; font-size: 13px; margin: 6px 0 10px; }
100
+ .grid dt { color: var(--muted); }
101
+ .grid dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
102
+ .external-links-label { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
103
+ .grid .external-links { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 0; }
104
+ .external-link { white-space: nowrap; }
105
+ .external-separator { color: var(--muted); white-space: pre; }
106
+ .external-links a.resolves { background: transparent; border: 0; color: var(--color-notice); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
107
+ .external-links .install.tone-notice { color: var(--color-notice); }
108
+ .external-links .install.tone-quiet { color: var(--color-quiet); }
109
+ .external-links [aria-disabled="true"] { color: var(--muted); }
110
+ .actions { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 12px; }
111
+ .action-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
112
+
113
+ .thread { border-top: 1px dashed var(--line); padding-top: 8px; }
114
+ .msg { margin: 8px 0; padding: 7px 10px; border-radius: 10px; max-width: 100%; }
115
+ .msg.claude { background: var(--claude); }
116
+ .msg.love { background: var(--love); }
117
+ .msg .who { font-size: 11px; color: var(--muted); margin-bottom: 2px; }
118
+ .msg .body { white-space: pre-wrap; overflow-wrap: anywhere; }
119
+ .msg .reply { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line); }
120
+ .thread form { margin-top: 8px; }
121
+ .thread-more { display: block; margin: -2px 0 2px -2px; padding: 8px 12px; background: none; border: 0; border-radius: 8px;
122
+ color: var(--accent); font-size: 12.5px; font-weight: 500; text-align: left; }
123
+ .thread-more:hover { text-decoration: underline; }
124
+ .thread-more:active { background: var(--accent-soft); }
125
+ .thread-more:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
126
+ .thread-more .n { color: var(--muted); font-weight: 400; }
127
+
128
+ @media (max-width: 560px) {
129
+ .item > summary { grid-template-columns: 16px 1fr; }
130
+ .item > summary .meta { grid-column: 2; flex-wrap: wrap; justify-content: flex-start; }
131
+ .detail { padding-left: 12px; }
132
+ .grid { grid-template-columns: 92px 1fr; }
133
+ }
134
+ .calls ul { margin: 4px 0 8px; padding-left: 18px; font-size: 13px; }
135
+ button.link { background: none; border: 0; color: var(--accent); padding: 8px 12px; font-size: 13px; cursor: pointer; }
136
+ .waves { display: grid; gap: 6px; margin-top: 4px; }
137
+ .wave { display: grid; grid-template-columns: 1fr auto; gap: 2px 8px; text-align: left; background: var(--panel);
138
+ border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; color: var(--text); }
139
+ .wave.on { border-color: var(--accent); background: var(--accent-soft); }
140
+ .wave-name { font-weight: 600; font-size: 13px; }
141
+ .wave .bar { grid-column: 1 / -1; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; }
142
+ .wave .bar span { display: block; height: 100%; background: var(--color-success); }
143
+ .nav a.icon { display: inline-flex; align-items: center; padding: 4px; color: var(--muted); }
144
+ .nav a.icon:hover { color: var(--text); }
145
+ .refresh { display: inline-flex; align-items: center; gap: 2px; line-height: 1.3; min-width: 0; }
146
+ .refresh #refresh { padding: 4px; margin-left: -4px; flex: none; }
147
+ .refresh #status { font-size: 12px; min-width: 0; }
148
+ .refresh .late { color: var(--color-notice); font-weight: 600; }
149
+ .lane-github { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--muted); text-align: right; }
150
+ .lane-github .late { color: var(--color-notice); }
151
+ .msg.other { background: transparent; border: 1px solid var(--line); }
152
+ .status.stale { border-color: var(--color-danger); background: conic-gradient(var(--color-danger) 0 75%, transparent 75% 100%); }
153
+ .msg .reply, .msg .small, .rep, .sent { overflow-wrap: anywhere; }
154
+ code { overflow-wrap: anywhere; }
155
+ /* iOS Safari zooms the page when a field under 16px takes focus, which shifts the page width. */
156
+ @media (pointer: coarse) { textarea, input, select { font-size: 16px; } }
157
+
158
+ /* Attachments */
159
+ .attach { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px;
160
+ border-radius: 8px; color: var(--muted); cursor: pointer; flex: none; overflow: hidden; }
161
+ .attach:hover, .attach:focus-within { color: var(--text); background: var(--chip); }
162
+ /* A file input draws its "Choose File" text at the 16px coarse-pointer size, wider than the button; iOS Safari widens the page to fit it. */
163
+ .attach input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; padding: 0; border: 0; font-size: 0; }
164
+ .row > .attach + .muted { flex: 1; min-width: 0; }
165
+ /* The ⌘↵ / Ctrl+↵ hint left of Send: only where there is a keyboard to press it on. */
166
+ .send-hint { display: none; color: var(--muted); font-size: 12px; white-space: nowrap; }
167
+ @media (hover: hover) and (pointer: fine) { .send-hint { display: inline; } }
168
+ .pending-files { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
169
+ .pending-files:empty { display: none; }
170
+ .pfile { position: relative; display: inline-flex; flex-direction: column; justify-content: center; width: 64px; height: 64px;
171
+ border: 1px solid var(--line); border-radius: 8px; background: var(--chip); overflow: hidden; font-size: 11px; }
172
+ .pfile img { width: 100%; height: 100%; object-fit: cover; }
173
+ .pfile .pname { padding: 4px 6px; overflow-wrap: anywhere; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
174
+ .pfile.uploading img, .pfile.uploading .pname { opacity: .55; }
175
+ .pfile .pbar { position: absolute; left: 4px; right: 4px; bottom: 4px; height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--panel) 70%, transparent); }
176
+ .pfile .pbar span { display: block; height: 100%; border-radius: 2px; background: var(--color-info); transition: width .2s; }
177
+ .pfile.error { border-color: var(--color-danger); }
178
+ .pfile .perr { position: absolute; left: 0; right: 0; bottom: 0; background: var(--color-danger); color: #fff; font-size: 10px; text-align: center; padding: 1px 2px; }
179
+ .pfile .unattach { position: absolute; top: 2px; right: 2px; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 50%;
180
+ background: rgba(0,0,0,.6); color: #fff; font-size: 11px; line-height: 22px; }
181
+ .files { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px; margin-top: 6px; }
182
+ .file-thumb { display: block; width: 96px; height: 96px; border-radius: 8px; overflow: hidden; border: 1px solid var(--line); background: var(--chip); }
183
+ .file-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
184
+ .file-link { display: inline-flex; align-items: center; max-width: 100%; padding: 3px 8px; border-radius: 6px; background: var(--chip);
185
+ font-size: 13px; overflow-wrap: anywhere; }
186
+ .file-link::before { content: "📎"; margin-right: 4px; }
187
+ .msg .body:empty { display: none; }
188
+ .note-tools { display: flex; align-items: flex-start; gap: 6px; }
189
+ .note-tools .pending-files { flex: 1; margin-top: 4px; }
190
+ .note-tools .attach { margin-left: auto; margin-top: 2px; }
191
+
192
+ /* Compose button and sheet */
193
+ main { padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
194
+ .fab { position: fixed; z-index: 6; right: calc(16px + env(safe-area-inset-right)); bottom: calc(var(--app-bottom, 0px) + 16px + env(safe-area-inset-bottom));
195
+ width: 56px; height: 56px; padding: 0; border-radius: 50%; border: 0; background: var(--accent); color: #fff;
196
+ display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 6px 18px rgba(0,0,0,.25); }
197
+ .fab:active { transform: scale(.96); }
198
+ .view-board .toast-stack { bottom: calc(var(--app-bottom, 0px) + 80px + env(safe-area-inset-bottom)); }
199
+ /* A phone sheet pads its content above the keyboard (--kb, dialogs.js fitSheet) and stays on the screen's bottom
200
+ edge while the keyboard covers the bar: iOS reports its height late, so moving the sheet up leaves a strip below. */
201
+ .sheet { position: fixed; inset: auto 0 0 0; margin: 0 auto; width: 100%; max-width: 560px;
202
+ max-height: calc(var(--vh, 100dvh) + var(--kb, 0px) - env(safe-area-inset-top) - 12px); overflow-y: auto; overscroll-behavior: contain;
203
+ padding: 0 0 var(--kb, 0px); border: 1px solid var(--line); border-bottom: 0; border-radius: 16px 16px 0 0; background: var(--panel); color: var(--text);
204
+ box-shadow: 0 -8px 30px rgba(0,0,0,.18); }
205
+ /* On phones, iOS scrolls the page under a modal dialog, and a drag on the dim area pans the view and Safari's toolbar
206
+ even when the page can't scroll; either moves a sheet pinned to the bottom and shows the board below it. */
207
+ @media (max-width: 899px) {
208
+ html:has(dialog:modal) { overflow: hidden; }
209
+ }
210
+ .sheet::backdrop { background: rgba(0,0,0,.35); touch-action: none; }
211
+ /* A phone sheet sits above the bottom bar, which already pads the safe area (--app-bottom). */
212
+ .sheet form { padding: 10px 16px max(12px, calc(env(safe-area-inset-bottom) - var(--app-bottom, 0px))); }
213
+ /* A sheet's last row of buttons stays in view at the foot of its scrolling content. Sticking stops at the scroll
214
+ area's bottom padding, so the shadow covers what scrolls through that padding. */
215
+ .sheet-actions { position: sticky; bottom: 0; z-index: 1; padding-top: 8px; background: var(--panel); box-shadow: 0 40px 0 var(--panel); }
216
+ .sheet.kb form { padding-bottom: 10px; }
217
+ .sheet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
218
+ .sheet-head h2 { font-size: 15px; margin: 0; }
219
+ .sheet textarea { resize: none; min-height: 96px; }
220
+ .sheet[open] { animation: sheet-in .22s ease-out; }
221
+ .sheet[open]::backdrop { animation: fade-in .22s ease-out; }
222
+ .sheet.closing { animation: sheet-out .18s ease-in forwards; }
223
+ .sheet.closing::backdrop { animation: fade-in .18s ease-in reverse forwards; }
224
+ @media (max-width: 699px) {
225
+ /* iOS's reported bottom edge can sit above the visible bottom; the form scrolls so the dialog can paint past it. */
226
+ .sheet.keyboard-sheet { max-width: none; overflow: visible; padding: 0; max-height: none;
227
+ --sheet-limit: max(80px, calc(var(--vh, 100dvh) + var(--kb, 0px) - max(12px, env(safe-area-inset-top)))); }
228
+ .sheet.keyboard-sheet::after { content: ''; position: absolute; top: calc(100% - 1px); left: -1px; right: -1px;
229
+ height: 100vh; height: 100lvh; background: var(--panel); pointer-events: none; }
230
+ .sheet.keyboard-sheet form { margin: 0; max-height: var(--sheet-limit); overflow-y: auto; overscroll-behavior: contain;
231
+ border-radius: 15px 15px 0 0; padding-bottom: calc(max(12px, calc(env(safe-area-inset-bottom) - var(--app-bottom, 0px))) + var(--kb, 0px)); }
232
+ .sheet.keyboard-sheet.kb form { padding-bottom: calc(10px + var(--kb, 0px)); }
233
+ #askText { font-size: 16px; }
234
+ }
235
+ @keyframes sheet-in { from { transform: translateY(100%); } }
236
+ @keyframes sheet-out { to { transform: translateY(100%); } }
237
+ @keyframes fade-in { from { opacity: 0; } }
238
+ .confirm-sheet { position: fixed; inset: 0; margin: 0; width: 100%; max-width: none; height: 100%; max-height: none; border: 0;
239
+ display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
240
+ gap: 8px; padding: 8px 8px calc(10px + env(safe-area-inset-bottom)); background: rgba(0,0,0,.4); animation: fade-in .18s ease-out; }
241
+ .confirm-sheet .box { width: min(520px, 100%); background: var(--panel); border-radius: 14px; overflow: hidden; text-align: center;
242
+ box-shadow: 0 10px 40px rgba(0,0,0,.3); animation: sheet-in .22s ease-out; }
243
+ .confirm-sheet p { margin: 0; padding: 14px 16px; font-size: 13.5px; line-height: 1.4; color: var(--muted); border-bottom: 1px solid var(--line); }
244
+ .confirm-sheet button { display: block; width: 100%; border: 0; border-radius: 0; background: transparent; padding: 14px 6px; font-size: 17px; color: var(--accent); }
245
+ .confirm-sheet .cancel { font-weight: 600; }
246
+ .confirm-sheet::backdrop { background: transparent; }
247
+ @media (min-width: 700px) {
248
+ .sheet { bottom: calc(var(--kb, 0px) + 24px); max-height: calc(var(--vh, 100dvh) - env(safe-area-inset-top) - 12px); padding-bottom: 0;
249
+ border-bottom: 1px solid var(--line); border-radius: 16px; }
250
+ .sheet form { padding-bottom: 14px; }
251
+ }
252
+ @media (prefers-reduced-motion: reduce) {
253
+ .sheet[open], .sheet[open]::backdrop, .sheet.closing, .sheet.closing::backdrop, .confirm-sheet, .confirm-sheet .box { animation: none; }
254
+ .fab:active { transform: none; }
255
+ }
256
+ /* The hub lives at the tailnet root, outside this site, so its link is root-absolute. */
257
+ .brand { align-items: center; }
258
+ .hub { display: inline-flex; flex: none; }
259
+ .hub img { width: 32px; height: auto; display: block; image-rendering: pixelated; }
260
+ @keyframes pulse { 50% { opacity: .35; } }
261
+ /* A lane's folds are inset boxes in its list, so they read as part of that lane, not as more lanes. */
262
+ .lane .list:has(> .fold) { padding-bottom: 8px; }
263
+ .fold { margin: 8px 8px 0; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
264
+ .list > .item + .fold { margin-top: 10px; }
265
+ .fold > summary { list-style: none; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 5px 12px; background: var(--panel); font-size: 13px; font-weight: 500; color: var(--text); }
266
+ .fold > summary::-webkit-details-marker { display: none; }
267
+ .fold > summary .count { color: var(--muted); font-weight: 400; }
268
+ .fold > summary:hover { background: color-mix(in srgb, var(--chip) 60%, transparent); }
269
+ .fold-list { border-top: 1px solid var(--line); background: color-mix(in srgb, var(--chip) 35%, transparent); }
270
+ .ask-actions { float: right; display: flex; gap: 2px; margin: -6px -6px 0 8px; }
271
+ .ask-actions .icon { padding: 6px; color: var(--muted); }
272
+ .ask-actions .icon:disabled { opacity: .35; }
273
+ .working-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--color-notice); margin-right: 5px; vertical-align: 1px; }
274
+ @media (prefers-reduced-motion: no-preference) { .working-dot { animation: pulse 1.4s ease-in-out infinite; } }
275
+ form[data-reply] .row .quick { margin-left: auto; margin-right: 6px; }
276
+ form[data-reply] .row [data-confirm="finish"] { margin-right: 6px; }
277
+ .q .proto { display: inline-block; margin: 0 0 8px; }
278
+ .msg .ask-actions { margin: -4px -6px 0 8px; }
279
+ button.attach { border: 0; background: transparent; padding: 0; }
280
+ button.attach:disabled { opacity: .35; }
281
+ .item > summary.work { grid-template-columns: 18px 1fr; align-items: start; }
282
+ .item > summary.work .status { margin-top: 2px; }
283
+ .line1 .chip { vertical-align: 1px; margin-right: 2px; }
284
+ .line2.meta { justify-content: flex-start; margin-top: 4px; }
285
+ .line3 { margin-top: 3px; }
286
+ .brand { display: grid; grid-template-columns: auto auto minmax(0, 1fr); column-gap: 10px; row-gap: 2px; }
287
+ .brand .hub { grid-row: 1 / span 2; align-self: center; }
288
+ .sub .working { color: var(--color-notice); }
289
+ .msg.link { padding: 0 10px; margin: 4px 0; font-size: 13px; }
290
+ .msg.builder-status { padding: 0 10px; margin: 4px 0; font-size: 12px; color: var(--color-notice); overflow-wrap: anywhere; }
291
+ .msg.builder-status .working-dot { animation: none; }
292
+ .msg.agent { padding: 0 10px; margin: 4px 0; font-size: 12px; font-weight: 600; overflow-wrap: anywhere; }
293
+ .msg.agent.started { color: var(--color-notice); } .msg.agent.finished { color: var(--color-success); }
294
+ .msg.agent .muted { font-weight: 400; }
295
+ .agent-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; margin-right: 5px; vertical-align: 1px; }
296
+ /* INT-30: card line order */
297
+ .line5.meta { justify-content: flex-start; margin-top: 4px; }
298
+ /* Lane headers: Linear-style group bands */
299
+ .lane > summary { display: flex; align-items: center; gap: 8px; margin: 0; min-height: 34px; padding: 5px 12px;
300
+ background: var(--band); border: 1px solid var(--line); border-radius: 10px; font-size: 13px; font-weight: 500; color: var(--text); }
301
+ .lane > summary { list-style: none; cursor: pointer; user-select: none; }
302
+ .lane > summary::-webkit-details-marker { display: none; }
303
+ .lane > summary:hover { background: color-mix(in srgb, var(--band), var(--text) 5%); }
304
+ .lane > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
305
+ .lane[open] > summary { border-bottom: 0; border-radius: 10px 10px 0 0; }
306
+ .lane[open] > .list { border-radius: 0 0 10px 10px; }
307
+ .lane > summary .count { color: var(--muted); font-weight: 400; }
308
+ /* INT-35: offline header */
309
+ .view-board.offline #compose { display: none; }
310
+ .view-board.offline main > * { opacity: .72; }
311
+ .view-board.offline main, .view-board.offline > .top > :not(.nav), .view-board.offline > .top .nav > :not(.refresh) { pointer-events: none; }
312
+ /* INT-28: answer banner */
313
+ .answer-banner { display: flex; align-items: center; gap: 4px; background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
314
+ border-radius: 10px; padding: 2px 4px 2px 0; margin-bottom: 12px; }
315
+ .answer-banner[hidden] { display: none; }
316
+ .answer-banner .answer-open { flex: 1; min-width: 0; display: flex; align-items: baseline; border: 0; background: transparent; padding: 8px 4px 8px 12px; text-align: left; white-space: nowrap; }
317
+ .answer-banner .answer-open strong { color: var(--accent); flex: none; }
318
+ .answer-banner .sep, .answer-banner .more { flex: none; white-space: pre; }
319
+ .answer-banner .more { color: var(--muted); }
320
+ .answer-banner .text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
321
+ .answer-banner .answer-dismiss { flex: none; display: inline-flex; padding: 6px; color: var(--muted); }
322
+ /* INT-37: one header row, the title cut with an ellipsis; the GitHub line's own refresh */
323
+ .top { gap: 8px; }
324
+ .brand { flex: 1 1 auto; display: flex; align-items: center; gap: 10px; min-width: 0; }
325
+ .brand :is(h1, h2) { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
326
+ .top .nav { flex: none; }
327
+ .lane-github { display: inline-flex; align-items: center; gap: 2px; }
328
+ .lane-github .github { display: inline-flex; align-items: center; gap: 4px; }
329
+ .lane-github .gh-mark { flex: none; }
330
+ .lane-github .gh-refresh { font-size: 15px; line-height: 1; padding: 2px 4px; margin-right: -6px; color: var(--muted); }
331
+ .lane-github .gh-refresh[aria-disabled="true"] { opacity: .35; cursor: default; }
332
+ /* INT-44: both refreshes rest and spin while they fetch */
333
+ .lane-github .gh-refresh { display: inline-flex; align-items: center; justify-content: center; }
334
+ .lane-github .gh-refresh.spinning[aria-disabled="true"] { opacity: .45; }
335
+ @media (prefers-reduced-motion: no-preference) {
336
+ #refresh.spinning svg, .gh-refresh.spinning svg { animation: refresh-spin .8s linear infinite; }
337
+ }
338
+ @keyframes refresh-spin { to { transform: rotate(360deg); } }
339
+ /* INT-55: the header's right group (the time then ↻, settings) sits one gap apart; each control paints disabled until app-header.js wires it */
340
+ .top .nav { gap: 8px; }
341
+ .top .nav .refresh { flex-direction: row-reverse; gap: 2px; }
342
+ .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; }
343
+ .top .nav #status { line-height: 1.1; white-space: nowrap; }
344
+ .top .nav #refresh:disabled, .top .nav a.icon[aria-disabled="true"] { opacity: .35; pointer-events: none; }
345
+ /* INT-64: offline, the header's time and refresh turn red */
346
+ .view-board.offline .refresh #refresh, .refresh .link-state.offline { color: var(--color-danger); }
347
+ .back { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px; color: var(--accent); text-decoration: none; }
348
+ /* 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
349
+ (docs/adr/0048-a-questions-takes-show-inline.md). The brand keeps that height when the rail hides the logo, so the
350
+ board's header and the sidebar's stay level. */
351
+ .top { padding-top: max(4px, env(safe-area-inset-top)); padding-bottom: 4px; }
352
+ .top .brand { gap: 4px; min-height: 44px; }
353
+ .top .hub, .top .back { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 44px; height: 44px; margin-left: -10px; border-radius: 10px; }
354
+ .top .back:active { background: var(--accent-soft); }
355
+ .top .brand :is(h1, h2) { font-size: 17px; font-weight: 600; line-height: 22px; }
356
+ /* The last visit paints dimmed until the fresh board lands; the controls ui-snapshot.js liveControls blocks rest meanwhile.
357
+ A changed card, or other changed line, gets a builder-orange border that fades once (app-snapshot.js flashChanged). */
358
+ main > * { transition: opacity .25s ease; }
359
+ .view-board[data-board="snapshot"]:not(.offline) main > * { opacity: .62; }
360
+ .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; }
361
+ .flash { animation: snapshot-flash 1.6s ease-out 1; border-radius: 6px; }
362
+ @keyframes snapshot-flash { from { box-shadow: inset 0 0 0 2px var(--color-notice); } to { box-shadow: inset 0 0 0 2px transparent; } }
363
+ .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; }
364
+ /* ADR 0040: the GitHub mark opens the Hub links sheet, with a red dot while a link is broken; a new board's setup checklist */
365
+ .lane-github .gh-links { position: relative; display: inline-flex; align-items: center; padding: 4px; margin: -4px -2px -4px -4px; font-size: inherit; line-height: 1; color: inherit; }
366
+ .lane-github .links-dot { position: absolute; top: 1px; right: 0; width: 7px; height: 7px; border-radius: 50%; background: var(--color-danger); box-shadow: 0 0 0 1.5px var(--bg); }
367
+ .sheet .sheet-body { padding: 10px 16px max(12px, calc(env(safe-area-inset-bottom) - var(--app-bottom, 0px))); }
368
+ .hub-links { list-style: none; margin: 0; padding: 0; }
369
+ .hub-links li { display: flex; align-items: baseline; gap: 10px; padding: 9px 0; border-top: 1px solid var(--line); min-width: 0; }
370
+ .hub-links li:first-child { border-top: 0; }
371
+ .hub-links li > span:last-child { min-width: 0; overflow-wrap: anywhere; }
372
+ .hub-links .dot { flex: none; transform: translateY(-1px); }
373
+ .hub-links strong, .hub-links .sub { display: block; }
374
+ .setup { background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); border-radius: 10px; padding: 10px 12px; margin-bottom: 12px; }
375
+ .setup h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; margin: 0 0 6px; color: var(--accent); }
376
+ .setup-steps { list-style: none; margin: 0; padding: 0; }
377
+ .setup-steps li { display: flex; align-items: baseline; gap: 8px; margin: 3px 0; overflow-wrap: anywhere; }
378
+ .setup-steps .dot { flex: none; }
379
+
380
+ /* The board's other views (view-settings.js, view-questions.js, view-notes.js) each scope their rules by the class on their root. */
381
+ .view-settings .set h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); margin: 18px 0 6px; }
382
+ .view-settings .who { display: flex; align-items: center; justify-content: space-between; padding: 9px 10px; border: 1px solid var(--line);
383
+ border-radius: 9px; margin: 6px 0; background: var(--panel); }
384
+ .view-settings .who.gone { opacity: .5; text-decoration: line-through; }
385
+ .view-settings .who.new { border-color: var(--accent); }
386
+ .view-settings .add { display: flex; gap: 6px; margin-top: 8px; }
387
+ .view-settings .add input { flex: 1; min-width: 0; }
388
+ .view-settings .replies .reply { margin-top: 8px; }
389
+ .view-settings label.who { gap: 12px; cursor: pointer; }
390
+ .view-settings label.who .sub { display: block; margin-top: 2px; }
391
+ .view-settings input.toggle { flex: none; width: 1.4rem; height: 1.4rem; margin: 0; accent-color: var(--accent); }
392
+ .view-settings .close-board-row { width: 100%; text-align: left; color: var(--color-danger); }
393
+ .view-settings .repo-row { width: 100%; gap: 12px; text-align: left; }
394
+ .view-settings .repo-row > :first-child { min-width: 0; overflow-wrap: anywhere; }
395
+ .view-settings .repo-row > :last-child { flex: none; }
396
+ .view-settings .close-board-row .sub { display: block; margin-top: 3px; }
397
+ .view-settings .close-board-sheet { text-align: left; line-height: 1.5; }
398
+ .view-settings .close-board-sheet h2 { color: var(--text); font-size: 18px; text-transform: none; letter-spacing: normal; }
399
+ .view-settings .close-board-sheet input { display: block; width: 100%; margin-top: 8px; }
400
+ .view-settings .close-board-sheet .danger { color: var(--color-danger); }
401
+ .view-settings .close-board-warning:has(li) { border: 1px solid var(--color-notice); padding: 0 12px; border-radius: 10px; margin-bottom: 14px; }
402
+ .view-questions .q { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin: 12px 0; }
403
+ .view-questions .q.urgent { border-color: var(--color-notice); }
404
+ .view-questions .q h3 { font-size: 15px; margin: 6px 0 4px; line-height: 1.3; }
405
+ .view-questions .ctx { color: var(--muted); font-size: 13px; margin-bottom: 8px; }
406
+ .view-questions .opt { display: grid; grid-template-columns: 22px 1fr; gap: 6px; padding: 8px; border: 1px solid var(--line);
407
+ border-radius: 8px; margin: 6px 0; cursor: pointer; }
408
+ .view-questions .opt:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
409
+ .view-questions .opt input { margin: 3px 0 0; width: 16px; height: 16px; accent-color: var(--accent); }
410
+ .view-questions .opt .lbl { font-weight: 600; }
411
+ .view-questions .opt .rec { color: var(--accent); font-weight: 500; font-size: 12px; margin-left: 4px; }
412
+ .view-questions .opt .det { color: var(--muted); font-size: 13px; }
413
+ .view-questions .note { margin-top: 4px; }
414
+ .view-questions .ans { background: var(--love); border-radius: 9px; padding: 7px 10px; margin-top: 8px; font-size: 13px; }
415
+ .view-questions .rep { background: var(--claude); border-radius: 9px; padding: 7px 10px; margin-top: 6px; font-size: 13px; }
416
+ .view-questions .q details { margin-top: 8px; }
417
+ .view-questions .q details > summary { cursor: pointer; color: var(--muted); font-size: 13px; }
418
+ .view-questions .topics { display: flex; gap: 6px; overflow-x: auto; padding: 2px 0 8px; margin-bottom: 4px; }
419
+ .view-questions .topics button { flex: none; border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; font-size: 13px;
420
+ background: var(--panel); color: var(--text); white-space: nowrap; }
421
+ .view-questions .topics button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }
422
+ .view-questions .topics .n { color: var(--muted); margin-left: 4px; }
423
+ .view-questions .topichead { margin: 2px 0 6px; }
424
+ .view-questions details.done > summary { cursor: pointer; color: var(--muted); font-size: 13px; margin: 16px 0 4px; }
425
+ .view-questions details.done .trow { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
426
+ .view-questions details.done button.ghost { font-size: 14px; text-align: left; padding: 6px 2px; }
427
+ .view-questions details.done .trow button { min-width: 0; }
428
+ .view-questions details.done .trow span { flex: none; }
429
+ .view-questions .bar { position: sticky; bottom: var(--app-bottom, 0px); padding: 10px 0 max(10px, env(safe-area-inset-bottom)); background: var(--bg);
430
+ display: flex; justify-content: space-between; align-items: center; gap: 8px; border-top: 1px solid var(--line); }
431
+ .view-questions .bar .send-hint { margin-left: auto; }
432
+ .view-questions .bar #qstatus { min-width: 0; }
433
+ .view-questions .bar #qstatus.sent { color: var(--color-success); font-weight: 600; }
434
+ .view-questions .solo { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin: 4px 0; }
435
+
436
+ /* 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
437
+ preview, and each row's takes top-aligned, since each frame takes its own page's height. */
438
+ .view-questions .takes { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 380px), 1fr)); gap: 10px; align-items: start; margin: 6px 0; }
439
+ .view-questions .take { min-width: 0; border: 1px solid var(--line); border-radius: 12px; background: var(--bg); padding: 10px; }
440
+ .view-questions .take:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
441
+ .view-questions .take .opt, .view-questions .take .opt:has(input:checked) { border: 0; padding: 0; margin: 0; background: none; }
442
+ /* view-questions.js sets a frame's height to its page's once measured; until then it shows nothing, so a take that turns
443
+ its question into the full-width layout never flashes cut off. */
444
+ .view-questions .take-shot { position: relative; display: block; margin-top: 8px; height: 360px; border: 1px solid var(--line); border-radius: 10px;
445
+ overflow: hidden; transition: height .15s ease; }
446
+ .view-questions .take-shot iframe { width: 100%; height: 100%; border: 0; background: var(--bg); }
447
+ .view-questions .take-shot.measuring { transition: none; }
448
+ .view-questions .take-shot.measuring iframe { visibility: hidden; }
449
+ .view-questions .take-shot.measuring::before { content: 'Loading take…'; position: absolute; inset: 0; display: grid; place-items: center;
450
+ color: var(--muted); font-size: 12.5px; }
451
+ .view-questions:has(.takes-full) main { max-width: none; }
452
+ .view-questions .takes-full { grid-template-columns: minmax(0, 1fr); }
453
+ .view-questions .take-choices { display: flex; flex-wrap: wrap; gap: 8px; }
454
+ .view-questions .take-choices .opt { flex: 1 1 160px; margin: 0; }
455
+ .view-questions .take-choices .opt[aria-current="true"] { border-color: var(--accent); }
456
+ .view-questions .takes-full .take { padding: 0; border: 0; }
457
+ .view-questions .takes-full .take-shot { margin-top: 0; transition: none; }
458
+
459
+ /* A take with nothing to see: boxes and arrows, then what it means in practice. */
460
+ .view-questions .flow { margin: 10px 0 4px; }
461
+ .view-questions .flow-row { display: grid; gap: 6px; }
462
+ .view-questions .flow-row.dense { gap: 4px; }
463
+ .view-questions .flow-box { position: relative; display: grid; place-items: center; border: 1.5px solid var(--line); border-radius: 8px;
464
+ background: var(--panel); padding: 6px 7px; font-size: 12px; line-height: 1.25; text-align: center; overflow-wrap: anywhere; hyphens: auto; }
465
+ .view-questions .flow-row.dense .flow-box { font-size: 11px; padding: 5px 2px; }
466
+ .view-questions .flow-box.in::before { content: ''; position: absolute; top: -6px; left: 50%; transform: translateX(-50%);
467
+ border: 5px solid transparent; border-top-color: var(--muted); border-bottom: 0; }
468
+ .view-questions .flow-box.tone-info { border-color: var(--color-info); }
469
+ .view-questions .flow-box.tone-notice { border-color: var(--color-notice); }
470
+ .view-questions .flow-box.tone-danger { border-color: var(--color-danger); }
471
+ .view-questions .flow-box.tone-success { border-color: var(--color-success); }
472
+ .view-questions .flow-box.tone-quiet { border-style: dashed; color: var(--muted); }
473
+ .view-questions .flow-conn { display: block; width: 100%; height: 18px; }
474
+ .view-questions .flow-conn line { stroke: var(--muted); stroke-width: 1.3; vector-effect: non-scaling-stroke; }
475
+ .view-questions .practice { font-size: 13px; margin-top: 8px; }
476
+ .view-questions .practice b { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
477
+ .view-questions .practice ul { margin: 4px 0 0; padding-left: 18px; }
478
+ .view-questions .practice li { margin: 3px 0; }
479
+ .view-notes .plan { overflow-wrap: anywhere; line-height: 1.5; }
480
+ .view-notes .plan h1 { font-size: 20px; line-height: 1.3; margin: 8px 0 10px; }
481
+ .view-notes .plan h2 { font-size: 17px; margin: 22px 0 8px; }
482
+ .view-notes .plan h3, .view-notes .plan h4, .view-notes .plan h5, .view-notes .plan h6 { font-size: 15px; margin: 18px 0 6px; }
483
+ .view-notes .plan p, .view-notes .plan ul, .view-notes .plan ol, .view-notes .plan blockquote { margin: 8px 0; }
484
+ .view-notes .plan ul, .view-notes .plan ol { padding-left: 22px; }
485
+ .view-notes .plan li { margin: 3px 0; }
486
+ .view-notes .plan li.nested { margin-left: 20px; }
487
+ .view-notes .plan code { font-size: 12.5px; background: var(--accent-soft); border-radius: 4px; padding: 0 3px; }
488
+ .view-notes .plan pre { overflow-x: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; }
489
+ .view-notes .plan pre code { background: none; padding: 0; overflow-wrap: normal; }
490
+ .view-notes .plan blockquote { border-left: 3px solid var(--line); padding-left: 10px; color: var(--muted); }
491
+ .view-notes .plan hr { border: 0; border-top: 1px solid var(--line); margin: 16px 0; }
492
+ .view-notes .plan a { color: var(--accent); }
493
+ .view-notes .md-table { overflow-x: auto; margin: 10px 0; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); }
494
+ .view-notes .md-table table { border-collapse: collapse; font-size: 13px; line-height: 1.4; }
495
+ .view-notes .md-table th, .view-notes .md-table td { text-align: left; vertical-align: top; padding: 6px 8px; border-bottom: 1px solid var(--line); min-width: 5em; overflow-wrap: normal; }
496
+ .view-notes .md-table th { background: var(--bg); font-weight: 600; }
497
+ .view-notes .md-table tr:last-child td { border-bottom: 0; }
498
+ .view-notes .md-table .wide { min-width: 14em; }
499
+ .view-notes .md-table .wider { min-width: 24em; }
500
+ /* A framed page (view-frame.js) fills the space beside the header, and scrolls inside its frame. */
501
+ .view-frame { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }
502
+ .view-frame > .top { flex: none; }
503
+ .view-frame.own-header > .top { display: none; }
504
+ .view-frame > .frame-page { flex: 1 1 auto; display: block; width: 100%; min-height: 0; border: 0; background: var(--bg); }
505
+ .view-frame .alone { white-space: nowrap; font-size: 13px; }
506
+
507
+ .top .phone-logo { display: none; }
508
+ @media (max-width: 899px) {
509
+ :root:has(body > .top, body.has-sidebar > :not(.sidebar) > .top):not(.ps-embed) {
510
+ --app-bottom: calc(49px + max(4px, env(safe-area-inset-bottom)));
511
+ }
512
+ body > .top, body.has-sidebar > :not(.sidebar) > .top {
513
+ position: fixed; top: auto; bottom: 0; left: 0; right: 0; z-index: 7;
514
+ border-bottom: 0; border-top: 1px solid var(--line); padding-top: 4px; padding-bottom: max(4px, env(safe-area-inset-bottom));
515
+ }
516
+ .top .phone-logo { display: inline-flex; margin-left: 0; }
517
+ .top .nav #refresh, .top .nav a.icon { min-width: 44px; min-height: 44px; }
518
+ 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)); }
519
+ .view-frame:not(.own-header) { padding-bottom: var(--app-bottom, 0px); }
520
+ .sheet { bottom: max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px)));
521
+ max-height: calc(var(--vh, 100dvh) + var(--kb, 0px) - max(var(--app-bottom, 0px), var(--kb, 0px)) - env(safe-area-inset-top) - 12px); }
522
+ .sheet.keyboard-sheet:not(.kb)::after { height: 0; }
523
+ .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)))); }
524
+ .confirm-sheet { padding-bottom: calc(10px + var(--app-bottom, 0px) + env(safe-area-inset-bottom)); }
525
+ }
526
+
527
+ .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));
528
+ 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)));
529
+ padding: 0; border: 1px solid var(--line); border-radius: 18px; background: var(--panel); color: var(--text);
530
+ overflow: hidden; box-shadow: 0 20px 70px #0004; }
531
+ .settings-modal[open] { display: flex; flex-direction: column; }
532
+ .settings-modal::backdrop { background: #0006; touch-action: none; }
533
+ .settings-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--line); }
534
+ .settings-head h2 { margin: 0; font-size: 18px; }
535
+ .settings-head p { margin: 4px 0 0; }
536
+ .settings-body { overflow-y: auto; overscroll-behavior: contain; min-height: 0; padding: 0 20px 16px; }
537
+ .settings-body input { font-size: 16px; }
538
+ .settings-footer { display: flex; justify-content: flex-end; padding: 12px 20px; border-top: 1px solid var(--line); }
539
+
540
+ .tk-section { margin: 10px 0; }
541
+ .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); }
542
+ .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; }
543
+ .tk-seg button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
544
+ .tk-seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
545
+ .tk-grid { display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); }
546
+ @media (min-width: 560px) { .tk-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); } }
547
+ .tk-field { display: block; min-width: 0; }
548
+ .tk-grid + .tk-field { margin-top: 8px; }
549
+ .tk-field > span { display: block; margin: 0 0 4px; font-size: 12px; font-weight: 600; }
550
+ .tk-field :is(select, input), .tk-new input { display: block; width: 100%; min-width: 0; min-height: 44px; padding: 8px 10px; box-sizing: border-box;
551
+ color: var(--text); background: var(--panel); border: 1px solid var(--line); border-radius: 8px; font: inherit; font-size: 16px; }
552
+ .tk-new { display: flex; gap: 6px; margin-top: 8px; }
553
+ .tk-new input { flex: 1; min-width: 0; font-size: 16px; }
554
+ .tk-detail > p { margin: 8px 0; }
555
+ .tk-hublink { color: var(--accent); text-decoration: none; }
556
+ .tk-hublink .sub, .tk-static .sub { display: block; margin-top: 2px; }
557
+
558
+ #newBoardSheet .tk-detail .who { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 9px 10px; border: 1px solid var(--line); border-radius: 9px; margin: 6px 0; background: var(--panel); }
559
+
560
+ .try-spin { display: inline-block; width: 10px; height: 10px; border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: try-spin .8s linear infinite; flex: none; }
561
+ @keyframes try-spin { to { transform: rotate(360deg); } }
562
+ @media (prefers-reduced-motion: reduce) { .try-spin { animation: none; } }
563
+ .install { display: inline-flex; align-items: center; gap: 4px; }
564
+ .external-links .install.tone-ready { color: var(--color-success); }
565
+ .external-links .install.tone-danger { color: var(--color-danger); }
566
+ .actions button[aria-disabled="true"] { opacity: .5; cursor: default; }
567
+ .tb-main { margin-top: 14px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding: 9px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); font-size: 13px; }
568
+ .tb-main svg { flex: none; color: var(--muted); }
569
+ .tb-main-text { flex: 1 1 200px; min-width: 0; overflow-wrap: anywhere; }
570
+ .tb-main .meta { justify-content: flex-end; margin-left: auto; }