app.css 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556
  1. /* ClankerSaddle application stylesheet — a THIN app-component layer
  2. on top of the Spry CSS framework (see docs/spry-conversion.md).
  3. Loaded AFTER spry.css. Every primitive Spry ships (typography,
  4. buttons, forms, tables, cards, spacing, dialogs, badges, expanders)
  5. is USED, not redefined: this file only carries ClankerSaddle's own
  6. components — status badges, the goal identity, the event log,
  7. chats, the terminal, toasts — and every colour is consumed through
  8. --spry-* custom properties so themes and dark mode just work.
  9. Sections are append-only: add new components under a header
  10. comment at the end of the matching section, never inline in the
  11. middle of another component's block. */
  12. /* ==========================================================================
  13. App tokens
  14. ========================================================================== */
  15. :root {
  16. /* Goal identity (plan 12): goals never read as tasks at a glance —
  17. their chrome carries this instead of the task accent. Derived
  18. from the Spry accent so themes can shift it coherently. */
  19. --goal: var(--spry-accent);
  20. /* A soft tint for goal surfaces (goalui pile cards). */
  21. --goal-surface: var(--spry-accent-soft);
  22. }
  23. /* ==========================================================================
  24. Shared chrome
  25. ========================================================================== */
  26. /* The emblem rides the topbar brand at its original 8rem size (the
  27. sampler review's explicit ask; the first conversion pass had
  28. restrained it to 2rem, which read as too small in review). */
  29. .brand-emblem {
  30. height: 8rem;
  31. width: auto;
  32. margin-block: -0.2rem;
  33. }
  34. /* The account flyout sits at the nav's right edge; its panel
  35. right-aligns so it never leaves the viewport. */
  36. .nav-auth { display: inline-flex; gap: var(--spry-space-1); margin-inline-start: auto; }
  37. .nav-auth .submenu { left: auto; right: 0; }
  38. /* The signed-in name leads the account menu, set off quietly. */
  39. .nav-user {
  40. color: var(--spry-fg-muted);
  41. font-weight: 600;
  42. padding: 0.4rem 0.75rem;
  43. font-size: 0.95em;
  44. }
  45. /* The goals nav entry keeps its weight (plan 12: goals are a peer of
  46. tasks, not a subpage). */
  47. .nav-goal { font-weight: 600; }
  48. .nav-goal .goal-icon { font-style: normal; margin-right: 0.15rem; }
  49. /* The 🎯 marker rides the nav entry, the goals pages and the goal
  50. pile cards. */
  51. .goal-icon { font-style: normal; margin-right: 0.15rem; }
  52. /* The goal head strip: id, title and lifecycle badges in one line. */
  53. .goal-strip { display: flex; gap: var(--spry-space-2); flex-wrap: wrap; align-items: center; }
  54. /* A quiet page footer under the container. */
  55. .site-footer {
  56. border-top: var(--spry-border-width) solid var(--spry-border);
  57. color: var(--spry-fg-muted);
  58. font-size: 0.85rem;
  59. margin-top: var(--spry-space-5);
  60. padding: var(--spry-space-3) var(--spry-space-2);
  61. text-align: center;
  62. }
  63. /* ==========================================================================
  64. Login
  65. ========================================================================== */
  66. .login {
  67. background:
  68. radial-gradient(60rem 24rem at 50% -8rem, var(--spry-accent-soft), transparent 70%),
  69. var(--spry-bg);
  70. display: flex;
  71. justify-content: center;
  72. min-height: 100vh;
  73. padding: var(--spry-space-5) var(--spry-space-2);
  74. }
  75. .login-card {
  76. align-self: center;
  77. max-width: 24rem;
  78. text-align: center;
  79. width: 100%;
  80. }
  81. .login-card .login-emblem { height: 6rem; width: auto; margin-inline: auto; }
  82. .login-card h1 { font-size: 1.5rem; margin: var(--spry-space-2) 0 0; }
  83. .login-card form { text-align: left; }
  84. .login-card form .field { margin-bottom: var(--spry-space-3); }
  85. .login-card button[type="submit"] { width: 100%; }
  86. /* ==========================================================================
  87. Status badges — the badge-colour contract (docs/spry-conversion.md)
  88. ========================================================================== */
  89. /* Spry's .badge is the base (accent tint). Statuses map onto the
  90. semantic Spry vars; page tasks extend this contract, never
  91. hard-coded hex. */
  92. .badge-draft,
  93. .badge-paused,
  94. .badge-cancelled,
  95. .badge-offline,
  96. .badge-reference,
  97. .badge-command,
  98. .badge-shell-reset,
  99. .badge-created,
  100. .badge-goal-paused,
  101. .badge-goal-cancelled,
  102. .badge-goal-created,
  103. .badge-goal-task-deleted,
  104. .badge-suggestion-declined,
  105. .badge-mode-implement {
  106. background: var(--spry-bg-alt);
  107. color: var(--spry-fg-muted);
  108. }
  109. .badge-queued,
  110. .badge-goal-task-enqueued {
  111. background: var(--spry-warning-soft);
  112. color: var(--spry-warning);
  113. }
  114. .badge-assigned,
  115. .badge-co-developed,
  116. .badge-user-comment,
  117. .badge-question,
  118. .badge-planning,
  119. .badge-goal-started,
  120. .badge-goal-resumed,
  121. .badge-goal-task-created,
  122. .badge-started,
  123. .badge-dispatch,
  124. .badge-query,
  125. .badge-mode-investigate,
  126. .badge-chat-awaiting_answer,
  127. .badge-kind-task,
  128. .badge-state_change { background: var(--spry-info-soft); color: var(--spry-info); }
  129. .badge-working,
  130. .badge-blocked,
  131. .badge-block,
  132. .badge-block-raised,
  133. .badge-goal-block-raised,
  134. .badge-suspended,
  135. .badge-review_requested,
  136. .badge-review,
  137. .badge-completion_review,
  138. .badge-mode-review,
  139. .badge-goal-completion-submitted,
  140. .badge-block-completion,
  141. .badge-busy,
  142. .badge-chat-running,
  143. .badge-suggestion-pending { background: var(--spry-warning-soft); color: var(--spry-warning); }
  144. .badge-done,
  145. .badge-idle,
  146. .badge-progress,
  147. .badge-complete,
  148. .badge-active,
  149. .badge-goal-plan-approved,
  150. .badge-goal-goal-approved,
  151. .badge-goal-task-done,
  152. .badge-block-answered,
  153. .badge-completed,
  154. .badge-mode-test,
  155. .badge-chat-idle,
  156. .badge-suggestion-approved { background: var(--spry-success-soft); color: var(--spry-success); }
  157. .badge-failed,
  158. .badge-goal-plan-rejected,
  159. .badge-goal-completion-rejected,
  160. .badge-goal-goal-failed,
  161. .badge-goal-task-failed,
  162. .badge-event-cancel { background: var(--spry-danger-soft); color: var(--spry-danger); }
  163. .badge-comment,
  164. .badge-goal-comment,
  165. .badge-tool { background: var(--spry-bg-inset); color: var(--spry-fg-muted); }
  166. .badge-plan,
  167. .badge-plan_review,
  168. .badge-rfc,
  169. .badge-mode-orchestrate,
  170. .badge-goal-plan-submitted,
  171. .badge-block-plan,
  172. .badge-admin,
  173. .badge-capability,
  174. .badge-kind-goal { background: var(--spry-accent-soft); color: var(--spry-accent); }
  175. .badge-yeehaw { background: var(--spry-warning-soft); color: var(--spry-warning); }
  176. /* Health badges on the saddles page: filled, readable on their own
  177. tint, semantic vars under. */
  178. .badge-health-healthy { background: var(--spry-success); color: var(--spry-accent-fg); }
  179. .badge-health-stale { background: var(--spry-warning); color: var(--spry-accent-fg); }
  180. .badge-health-fenced { background: var(--spry-danger-soft); color: var(--spry-danger); }
  181. .badge-health-offline { background: var(--spry-danger); color: var(--spry-accent-fg); }
  182. .badge-health-unknown { background: var(--spry-bg-inset); color: var(--spry-fg-muted); }
  183. /* The × inside a capability chip (badge) */
  184. .chip-remove {
  185. background: none;
  186. border: none;
  187. color: inherit;
  188. cursor: pointer;
  189. font-size: 0.85rem;
  190. line-height: 1;
  191. margin-left: 0.25rem;
  192. padding: 0;
  193. }
  194. /* ==========================================================================
  195. Cards & layout shims
  196. ========================================================================== */
  197. /* A responsive card grid (Spry has no auto-fill card grid). */
  198. .cards {
  199. display: grid;
  200. grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  201. gap: var(--spry-space-3);
  202. margin: var(--spry-space-3) 0;
  203. }
  204. .card h3 { margin: 0 0 var(--spry-space-2); }
  205. /* The task page's status block — the left cell of the status/
  206. description grid (plan 11 task D). */
  207. .status-head { border-left: 4px solid var(--spry-accent); }
  208. /* Status + description side by side (plan 11 task D); stacks
  209. status-then-description on small screens. */
  210. .grid-2 {
  211. display: grid;
  212. grid-template-columns: 1fr 1fr;
  213. gap: var(--spry-space-3);
  214. align-items: start;
  215. margin: var(--spry-space-3) 0;
  216. }
  217. /* The status card's corner badge floats right of its heading. */
  218. .status-corner { float: right; font-size: 0.9rem; }
  219. /* Edit forms keep a readable measure — the .field stack (Spry) plus
  220. this cap for long inputs. */
  221. .form-rows { display: flex; flex-direction: column; gap: var(--spry-space-3); }
  222. .form-rows .field { max-width: 34rem; }
  223. /* ==========================================================================
  224. Markdown bodies (stm-markdown)
  225. ========================================================================== */
  226. /* Spry styles bare markdown elements; the app's .md containers tame
  227. the page-level heading scale inside cards — an agent report's h1
  228. is a section head, not the page title. */
  229. .md h1 { font-size: 1.35rem; margin: var(--spry-space-3) 0 var(--spry-space-2); }
  230. .md h2 { font-size: 1.2rem; margin: var(--spry-space-3) 0 var(--spry-space-2); }
  231. .md h3 { font-size: 1.05rem; margin: var(--spry-space-3) 0 var(--spry-space-2); }
  232. .md p, .md ul, .md ol { margin: var(--spry-space-2) 0; }
  233. .md > :first-child { margin-top: 0; }
  234. .md > :last-child { margin-bottom: 0; }
  235. /* ==========================================================================
  236. Comments
  237. ========================================================================== */
  238. /* Footnote treatment (plan 13 B4 / plan 15 C4): comments read as
  239. muted, smaller footnotes — the body leads, the chrome trails. */
  240. .comment {
  241. background: var(--spry-surface);
  242. border: var(--spry-border-width) solid var(--spry-border);
  243. border-left: 3px solid var(--spry-border);
  244. border-radius: var(--spry-radius);
  245. color: var(--spry-fg-muted);
  246. font-size: 0.88rem;
  247. margin: var(--spry-space-2) 0;
  248. padding: var(--spry-space-2) var(--spry-space-3);
  249. }
  250. .comment.agent { border-left-color: var(--spry-accent); }
  251. /* Harness/system-authored rows render as a distinct SYSTEM event
  252. (plan 15 C2): labelled, muted, dashed rail, never an agent row. */
  253. .comment.system {
  254. background: var(--spry-bg-inset);
  255. border-left: 3px dashed var(--spry-border);
  256. font-size: 0.82rem;
  257. }
  258. .comment.system .system-label {
  259. border: var(--spry-border-width) solid var(--spry-border);
  260. border-radius: var(--spry-radius-pill);
  261. color: var(--spry-fg-muted);
  262. font-size: 0.7rem;
  263. font-weight: 600;
  264. letter-spacing: 0.04em;
  265. margin-right: 0.35rem;
  266. padding: 0.05rem 0.5rem;
  267. text-transform: uppercase;
  268. }
  269. .comment blockquote {
  270. border-left: 3px solid var(--spry-border);
  271. color: var(--spry-fg-muted);
  272. margin: var(--spry-space-2) 0;
  273. padding: 0 var(--spry-space-2);
  274. white-space: pre-wrap;
  275. }
  276. .comment .comment-meta { margin: var(--spry-space-2) 0 0; }
  277. /* The comment footnote utility (dashboard timeline rows, goal
  278. narrative events). */
  279. .footnote { color: var(--spry-fg-muted); font-size: 0.85rem; }
  280. /* ==========================================================================
  281. Blocks (questions, RFCs, reviews)
  282. ========================================================================== */
  283. .block-card {
  284. background: var(--spry-surface);
  285. border: var(--spry-border-width) solid var(--spry-border);
  286. border-left: 4px solid var(--spry-accent);
  287. border-radius: var(--spry-radius);
  288. margin: var(--spry-space-3) 0;
  289. padding: var(--spry-space-3);
  290. }
  291. .block-card.rfc { border-left-color: var(--spry-accent); }
  292. .block-card.review { border-left-color: var(--spry-warning); }
  293. .block-card.answered { border-left-color: var(--spry-border); opacity: 0.85; }
  294. .block-card .options { display: flex; flex-direction: column; gap: var(--spry-space-2); margin: var(--spry-space-2) 0; }
  295. .block-card .options label { display: flex; gap: var(--spry-space-2); align-items: baseline; font-weight: normal; }
  296. .block-card textarea { min-height: 4rem; }
  297. /* Clickable option cards: the whole label selects its radio, and a
  298. checked card highlights (evergreen :has). */
  299. .options .option-card {
  300. align-items: center;
  301. display: block;
  302. margin-bottom: var(--spry-space-2);
  303. border: var(--spry-border-width) solid var(--spry-border);
  304. border-radius: var(--spry-radius);
  305. cursor: pointer;
  306. padding: var(--spry-space-2) var(--spry-space-3);
  307. }
  308. .options .option-card:hover { border-color: var(--spry-accent); }
  309. .options .option-card:has(input:checked) {
  310. background: var(--spry-accent-soft);
  311. border-color: var(--spry-accent);
  312. }
  313. /* ==========================================================================
  314. Plan review & review banner
  315. ========================================================================== */
  316. .plan-review .actions { display: flex; gap: var(--spry-space-2); flex-wrap: wrap; margin: var(--spry-space-2) 0; }
  317. .plan-review .rfc-body {
  318. border: var(--spry-border-width) solid var(--spry-border);
  319. border-radius: var(--spry-radius);
  320. margin: var(--spry-space-2) 0;
  321. padding: var(--spry-space-3);
  322. }
  323. .plan-comments { list-style: none; margin: var(--spry-space-2) 0; padding: 0; }
  324. .plan-comments li {
  325. border: var(--spry-border-width) solid var(--spry-border);
  326. border-left: 3px solid var(--spry-accent);
  327. border-radius: var(--spry-radius);
  328. margin: var(--spry-space-2) 0;
  329. padding: var(--spry-space-2) var(--spry-space-3);
  330. }
  331. .plan-comments blockquote { margin: 0 0 var(--spry-space-2); }
  332. .plan-popover {
  333. background: var(--spry-popover-bg);
  334. border: var(--spry-border-width) solid var(--spry-popover-border);
  335. border-radius: var(--spry-radius);
  336. box-shadow: var(--spry-popover-shadow);
  337. padding: var(--spry-space-3);
  338. position: absolute;
  339. width: 22rem;
  340. z-index: 40;
  341. }
  342. .plan-popover .actions { display: flex; gap: var(--spry-space-2); margin-top: var(--spry-space-2); }
  343. /* The review banner: a warning-tinted alert panel. */
  344. .review-banner {
  345. background: var(--spry-warning-soft);
  346. border: var(--spry-border-width) solid var(--spry-warning);
  347. border-radius: var(--spry-radius);
  348. margin: var(--spry-space-3) 0;
  349. padding: var(--spry-space-3);
  350. }
  351. .review-banner h3 { color: var(--spry-warning); margin: 0 0 var(--spry-space-2); }
  352. .review-banner .actions { display: flex; gap: var(--spry-space-2); flex-wrap: wrap; margin-top: var(--spry-space-2); }
  353. /* ==========================================================================
  354. Progress
  355. ========================================================================== */
  356. /* The goal page's card rhythm (plan 15 C4): every section is a card
  357. and the cards breathe between themselves. */
  358. .goal-page .card { margin-bottom: var(--spry-space-3); }
  359. .goal-page .card:last-child { margin-bottom: 0; }
  360. .progress-latest {
  361. background: var(--spry-success-soft);
  362. border: var(--spry-border-width) solid var(--spry-success);
  363. border-radius: var(--spry-radius);
  364. font-weight: 600;
  365. margin: var(--spry-space-2) 0;
  366. padding: var(--spry-space-2) var(--spry-space-3);
  367. }
  368. .progress-history li { margin: var(--spry-space-2) 0; }
  369. /* ==========================================================================
  370. Goal identity
  371. ========================================================================== */
  372. /* The goal page's status card: the goal accent replaces the task
  373. accent of .status-head. */
  374. .goal-head { border-left: 4px solid var(--goal); }
  375. /* Dashboard pile cards for goal items: goal accent + tinted surface. */
  376. .card.goal-card {
  377. background: var(--goal-surface);
  378. border-left: 4px solid var(--goal);
  379. }
  380. /* The supervision narrative's markdown bodies flow inside the flex
  381. event row. */
  382. .goal-event-text { flex: 1 1 16rem; min-width: 0; }
  383. .goal-event-text > :first-child { margin-top: 0; }
  384. .goal-event-text > :last-child { margin-bottom: 0; }
  385. /* The current-activity card on the goal page. */
  386. .goal-activity code { word-break: break-all; }
  387. /* Filter pills (goal page, event log toolbar). */
  388. .goal-filter,
  389. .log-filter {
  390. background: transparent;
  391. border: var(--spry-border-width) solid var(--spry-border);
  392. border-radius: var(--spry-radius-pill);
  393. color: var(--spry-fg-muted);
  394. cursor: pointer;
  395. font-size: 0.8rem;
  396. padding: 0.15rem 0.7rem;
  397. }
  398. .goal-filter:hover,
  399. .log-filter:hover { border-color: var(--spry-fg-muted); color: var(--spry-fg); }
  400. .goal-filter.active,
  401. .log-filter.active {
  402. background: var(--spry-accent-soft);
  403. border-color: var(--spry-accent);
  404. color: var(--spry-accent);
  405. }
  406. .goal-event {
  407. border-left: 2px solid var(--spry-border);
  408. display: flex;
  409. gap: 0.5rem;
  410. flex-wrap: wrap;
  411. margin: 0.35rem 0;
  412. padding-left: 0.6rem;
  413. }
  414. /* Plan 14 B6: comment events read as FOOTNOTES in the narrative. */
  415. .goal-event.footnote {
  416. border-left-color: transparent;
  417. font-size: 0.85rem;
  418. margin: 0.15rem 0;
  419. opacity: 0.75;
  420. padding-left: 0.6rem;
  421. }
  422. /* Plan 14 B5: the goal's Rules section rows. */
  423. .rule-card {
  424. border: var(--spry-border-width) solid var(--spry-border);
  425. border-radius: var(--spry-radius-sm);
  426. margin: var(--spry-space-2) 0;
  427. padding: var(--spry-space-2) var(--spry-space-3);
  428. }
  429. .rule-card > h4 { margin: 0 0 var(--spry-space-2); }
  430. .plan-revision { margin: 0.5rem 0; }
  431. /* ==========================================================================
  432. Text-selection quote button (the comment popover's only survivor)
  433. ========================================================================== */
  434. /* T11 sweep: the terminal surface (.terminal + the .term-entry
  435. family, ~120 lines) and the .quote-popover pair are deleted — the
  436. terminal page died with plan 17's event-log migration and app.js's
  437. quote engine creates only this floating .quote-button + the dialog.
  438. The event log's own row grammar lives in the Event log section. */
  439. .quote-button {
  440. background: var(--spry-accent);
  441. border: none;
  442. border-radius: var(--spry-radius-sm);
  443. color: var(--spry-accent-fg);
  444. font-size: 0.8rem;
  445. padding: 0.3rem 0.7rem;
  446. position: absolute;
  447. z-index: 41;
  448. }
  449. /* ==========================================================================
  450. Toasts
  451. ========================================================================== */
  452. .toast-stack {
  453. bottom: var(--spry-space-3);
  454. display: flex;
  455. flex-direction: column;
  456. gap: var(--spry-space-2);
  457. max-width: min(24rem, calc(100vw - 3rem));
  458. position: fixed;
  459. right: var(--spry-space-3);
  460. z-index: 100;
  461. }
  462. .toast {
  463. animation: toast-in 0.25s ease-out;
  464. background: var(--spry-surface);
  465. border-left: 4px solid var(--spry-accent);
  466. border-radius: var(--spry-radius-sm);
  467. box-shadow: var(--spry-shadow-lg);
  468. font-size: 0.9rem;
  469. padding: var(--spry-space-2) var(--spry-space-3);
  470. }
  471. .toast.success { border-left-color: var(--spry-success); }
  472. .toast.error { border-left-color: var(--spry-danger); }
  473. .toast.leaving { animation: toast-out 0.25s ease-in forwards; }
  474. @keyframes toast-in {
  475. from { opacity: 0; transform: translateX(1rem); }
  476. to { opacity: 1; transform: translateX(0); }
  477. }
  478. @keyframes toast-out {
  479. from { opacity: 1; transform: translateX(0); }
  480. to { opacity: 0; transform: translateX(1rem); }
  481. }
  482. /* ==========================================================================
  483. Comment dialog (quote → comment)
  484. ========================================================================== */
  485. dialog#quote-comment-dialog blockquote {
  486. background: var(--spry-bg-inset);
  487. border-left: 3px solid var(--spry-accent);
  488. font-family: var(--spry-font-mono);
  489. font-size: 0.85rem;
  490. margin: 0 0 var(--spry-space-3);
  491. max-height: 10rem;
  492. overflow-y: auto;
  493. padding: var(--spry-space-3);
  494. white-space: pre-wrap;
  495. }
  496. .term-comment-quote { color: var(--spry-success); font-style: italic; }
  497. /* ==========================================================================
  498. Event log (task_log.html) — the live console
  499. ========================================================================== */
  500. .log-page h2 { margin: 0; }
  501. .log-back { margin: 0 0 var(--spry-space-2); }
  502. .log-header {
  503. align-items: flex-end;
  504. display: flex;
  505. flex-wrap: wrap;
  506. gap: var(--spry-space-3);
  507. justify-content: space-between;
  508. margin-bottom: var(--spry-space-3);
  509. }
  510. .log-title .muted { margin: 0.25rem 0 0; }
  511. .log-actions { align-items: center; display: flex; gap: var(--spry-space-2); }
  512. .log-live {
  513. align-items: center;
  514. background: var(--spry-success-soft);
  515. border: var(--spry-border-width) solid var(--spry-success);
  516. border-radius: var(--spry-radius-pill);
  517. color: var(--spry-success);
  518. display: inline-flex;
  519. font-size: 0.8rem;
  520. gap: 0.4rem;
  521. padding: 0.2rem 0.7rem;
  522. }
  523. .log-live.retry {
  524. background: var(--spry-warning-soft);
  525. border-color: var(--spry-warning);
  526. color: var(--spry-warning);
  527. }
  528. /* A non-live task (parked, blocked, done...) names its state: the
  529. status palette carries the colour, the dot holds still. */
  530. .log-live.still .live-dot { animation: none; opacity: 0.85; }
  531. .log-live.still.badge-suspended {
  532. background: var(--spry-accent-soft);
  533. border-color: var(--spry-accent);
  534. color: var(--spry-accent);
  535. }
  536. .log-live.still.badge-draft {
  537. background: var(--spry-bg-alt);
  538. border-color: var(--spry-border);
  539. color: var(--spry-fg-muted);
  540. }
  541. .log-live.still.badge-queued {
  542. background: var(--spry-warning-soft);
  543. border-color: var(--spry-warning);
  544. color: var(--spry-warning);
  545. }
  546. .log-live.still.badge-blocked,
  547. .log-live.still.badge-review_requested {
  548. background: var(--spry-warning-soft);
  549. border-color: var(--spry-warning);
  550. color: var(--spry-warning);
  551. }
  552. .log-live.still.badge-done {
  553. background: var(--spry-success-soft);
  554. border-color: var(--spry-success);
  555. color: var(--spry-success);
  556. }
  557. .log-live.still.badge-failed {
  558. background: var(--spry-danger-soft);
  559. border-color: var(--spry-danger);
  560. color: var(--spry-danger);
  561. }
  562. .live-dot {
  563. animation: live-pulse 1.6s ease-in-out infinite;
  564. background: currentColor;
  565. border-radius: 50%;
  566. height: 0.5rem;
  567. width: 0.5rem;
  568. }
  569. @keyframes live-pulse {
  570. 0%, 100% { opacity: 1; }
  571. 50% { opacity: 0.3; }
  572. }
  573. .log-shell {
  574. background: var(--spry-surface);
  575. border: var(--spry-border-width) solid var(--spry-border);
  576. border-radius: var(--spry-radius);
  577. box-shadow: var(--spry-shadow);
  578. overflow: hidden;
  579. }
  580. .log-toolbar {
  581. align-items: center;
  582. background: var(--spry-bg-alt);
  583. border-bottom: var(--spry-border-width) solid var(--spry-border);
  584. display: flex;
  585. flex-wrap: wrap;
  586. gap: var(--spry-space-2);
  587. padding: var(--spry-space-2) var(--spry-space-3);
  588. }
  589. .log-toolbar-label { color: var(--spry-fg-muted); font-size: 0.8rem; }
  590. .log-toolbar input[type="search"] {
  591. background: var(--spry-surface);
  592. border: var(--spry-border-width) solid var(--spry-border);
  593. border-radius: var(--spry-radius-pill);
  594. color: var(--spry-fg);
  595. flex: 1;
  596. min-width: 12rem;
  597. padding: 0.3rem 0.9rem;
  598. }
  599. .log-toolbar input[type="search"]::placeholder { color: var(--spry-fg-muted); }
  600. .log-more {
  601. background: transparent;
  602. border: 1px dashed var(--spry-border);
  603. color: var(--spry-accent);
  604. display: block;
  605. margin: 0 auto var(--spry-space-3);
  606. padding: 0.25rem 0.9rem;
  607. }
  608. .log-more:hover { background: var(--spry-bg-alt); text-decoration: none; }
  609. .log-feed {
  610. display: flex;
  611. flex-direction: column;
  612. gap: var(--spry-space-2);
  613. max-height: 75vh;
  614. overflow-y: auto;
  615. padding: var(--spry-space-3);
  616. scrollbar-color: var(--spry-border) transparent;
  617. scrollbar-width: thin;
  618. }
  619. .log-feed::-webkit-scrollbar { width: 10px; }
  620. .log-feed::-webkit-scrollbar-thumb {
  621. background: var(--spry-border);
  622. border-radius: 5px;
  623. }
  624. .log-feed::-webkit-scrollbar-track { background: transparent; }
  625. .log-empty {
  626. color: var(--spry-fg-muted);
  627. padding: var(--spry-space-4) 0;
  628. text-align: center;
  629. }
  630. /* Turns hang from a rail; their events sit under it. */
  631. .turn-card {
  632. border-left: 2px solid var(--spry-border);
  633. display: flex;
  634. flex-direction: column;
  635. gap: 0.4rem;
  636. margin: var(--spry-space-2) 0;
  637. padding: 0.2rem 0 0.2rem var(--spry-space-3);
  638. }
  639. .turn-head {
  640. align-items: center;
  641. color: var(--spry-fg-muted);
  642. display: flex;
  643. font-size: 0.7rem;
  644. gap: 0.5rem;
  645. letter-spacing: 0.06em;
  646. margin: 0;
  647. text-transform: uppercase;
  648. }
  649. .turn-chip {
  650. background: var(--spry-accent-soft);
  651. border: var(--spry-border-width) solid var(--spry-accent);
  652. border-radius: var(--spry-radius-pill);
  653. color: var(--spry-accent);
  654. padding: 0.05rem 0.6rem;
  655. }
  656. /* Event rows: a glyph gutter carries the kind, the chip names it. */
  657. .event-row {
  658. border-radius: var(--spry-radius-sm);
  659. padding: 0.1rem 0.4rem 0.1rem 1.8rem;
  660. position: relative;
  661. }
  662. .event-row:hover { background: var(--spry-bg-alt); }
  663. .event-row::before {
  664. color: var(--spry-fg-muted);
  665. content: "\00b7";
  666. display: inline-block;
  667. font-family: var(--spry-font-mono);
  668. font-size: 0.9rem;
  669. left: 0.35rem;
  670. position: absolute;
  671. text-align: center;
  672. top: 0.2rem;
  673. width: 1rem;
  674. }
  675. .event-row.event-kind-tool_call::before { color: var(--spry-accent); content: "\25b8"; }
  676. .event-row.event-kind-tool_result::before { content: "\21b3"; }
  677. .event-row.event-kind-turn_text::before { color: var(--spry-accent); content: "\2726"; }
  678. .event-row.event-kind-command_output::before { color: var(--spry-accent); content: "\203a"; }
  679. .event-row.event-kind-progress::before { color: var(--spry-success); content: "\25c6"; }
  680. .event-row.event-kind-comment::before { color: var(--spry-warning); content: "\201c"; }
  681. .event-row.event-kind-answer::before { color: var(--spry-success); content: "\2713"; }
  682. .event-row.event-kind-verdict::before { color: var(--spry-warning); content: "\2261"; }
  683. .event-row.event-kind-usage::before { content: "\2211"; }
  684. .event-row.event-kind-state_change::before { color: var(--spry-accent); content: "\2192"; }
  685. .event-row.event-kind-lifecycle::before { color: var(--spry-accent); content: "\25cf"; }
  686. .event-row.event-kind-force_wake::before { color: var(--spry-warning); content: "\21d1"; }
  687. .event-row.event-kind-cancel::before { color: var(--spry-danger); content: "\00d7"; }
  688. .event-row.event-kind-yeehaw::before { color: var(--spry-warning); content: "\2605"; }
  689. .event-row .event-head {
  690. align-items: baseline;
  691. display: flex;
  692. flex-wrap: wrap;
  693. gap: 0.5rem;
  694. margin: 0.15rem 0;
  695. }
  696. .log-feed .badge {
  697. border-radius: var(--spry-radius-pill);
  698. font-size: 0.7rem;
  699. letter-spacing: 0.03em;
  700. padding: 0.05rem 0.55rem;
  701. }
  702. .event-kind-turn_text { background: var(--spry-accent-soft); color: var(--spry-accent); }
  703. .event-kind-tool_call { background: var(--spry-info-soft); color: var(--spry-info); }
  704. .event-kind-command_output { background: var(--spry-info-soft); color: var(--spry-info); }
  705. .event-kind-tool_result,
  706. .event-kind-usage { background: var(--spry-bg-alt); color: var(--spry-fg-muted); }
  707. .event-kind-progress,
  708. .event-kind-answer { background: var(--spry-success-soft); color: var(--spry-success); }
  709. .event-kind-comment,
  710. .event-kind-verdict,
  711. .event-kind-force_wake { background: var(--spry-warning-soft); color: var(--spry-warning); }
  712. .event-kind-state_change { background: var(--spry-info-soft); color: var(--spry-info); }
  713. .event-kind-lifecycle { background: var(--spry-accent-soft); color: var(--spry-accent); }
  714. .event-kind-cancel { background: var(--spry-danger-soft); color: var(--spry-danger); }
  715. .event-kind-yeehaw { background: var(--spry-warning-soft); color: var(--spry-warning); }
  716. .log-feed p { margin: 0.15rem 0; }
  717. .event-pre {
  718. background: var(--spry-bg-inset);
  719. border: var(--spry-border-width) solid var(--spry-border);
  720. border-radius: var(--spry-radius-sm);
  721. color: var(--spry-fg);
  722. font-size: 0.8rem;
  723. margin: 0.2rem 0;
  724. max-height: 20rem;
  725. overflow: auto;
  726. padding: 0.3rem 0.5rem;
  727. white-space: pre-wrap;
  728. word-break: break-word;
  729. }
  730. .event-pre.event-command {
  731. background: var(--spry-info-soft);
  732. border-color: var(--spry-info);
  733. color: var(--spry-info);
  734. }
  735. .event-pre.event-stderr {
  736. background: var(--spry-danger-soft);
  737. border-color: var(--spry-danger);
  738. color: var(--spry-danger);
  739. }
  740. .event-turn {
  741. background: transparent;
  742. border-color: var(--spry-accent);
  743. border-left: 3px solid var(--spry-accent);
  744. color: var(--spry-fg-muted);
  745. }
  746. .event-question { color: var(--spry-warning); font-style: italic; }
  747. .event-usage {
  748. color: var(--spry-fg-muted);
  749. font-family: var(--spry-font-mono);
  750. font-size: 0.8rem;
  751. }
  752. .event-fold { margin: 0.2rem 0; }
  753. .event-fold > summary {
  754. color: var(--spry-fg-muted);
  755. cursor: pointer;
  756. font-size: 0.8rem;
  757. }
  758. .event-fold > summary:hover { color: var(--spry-fg); }
  759. .event-fold[open] > summary { margin-bottom: 0.2rem; }
  760. /* Tool results: short ones render inline under a quiet metric
  761. line; long ones collapse behind the coloured summary. */
  762. .event-result > summary {
  763. color: var(--spry-success);
  764. cursor: pointer;
  765. font-family: var(--spry-font-mono);
  766. font-size: 0.8rem;
  767. }
  768. .event-result > summary.failed { color: var(--spry-danger); }
  769. .event-result > summary::marker { color: var(--spry-fg-muted); }
  770. /* Tool results ride attached to their call. */
  771. .event-attached {
  772. border-left: 2px solid var(--spry-border);
  773. margin: 0.3rem 0 0.1rem 0.4rem;
  774. padding-left: 0.75rem;
  775. }
  776. .event-attached > .result-meta { margin: 0; }
  777. .event-attached .event-result { margin: 0.1rem 0; }
  778. .event-attached .event-pre {
  779. max-height: 16rem;
  780. overflow: auto;
  781. }
  782. .event-result-body { margin-top: 0.3rem; }
  783. .result-ok { color: var(--spry-success); }
  784. .result-meta {
  785. color: var(--spry-fg-muted);
  786. font-family: var(--spry-font-mono);
  787. font-size: 0.75rem;
  788. }
  789. .result-tasks { margin: 0.2rem 0; padding-left: 1.2rem; }
  790. .result-tasks li { margin: 0.15rem 0; }
  791. .result-tasks .badge { margin-left: 0.5rem; }
  792. .log-feed blockquote {
  793. border-left: 3px solid var(--spry-border);
  794. color: var(--spry-fg);
  795. margin: 0.2rem 0;
  796. padding: 0 0.75rem;
  797. }
  798. .log-feed .yeehaw-message { color: var(--spry-warning); }
  799. .event-diff {
  800. background: var(--spry-surface);
  801. border: var(--spry-border-width) solid var(--spry-border);
  802. border-radius: var(--spry-radius-sm);
  803. font-family: var(--spry-font-mono);
  804. font-size: 0.8rem;
  805. margin: 0.2rem 0;
  806. max-height: 24rem;
  807. overflow: auto;
  808. padding: 0.3rem 0;
  809. }
  810. .diff-line {
  811. padding: 0 0.6rem;
  812. white-space: pre-wrap;
  813. word-break: break-word;
  814. }
  815. @media (max-width: 40rem) {
  816. .log-header { align-items: flex-start; flex-direction: column; }
  817. }
  818. /* ==========================================================================
  819. Advisor chats (goal 27)
  820. ========================================================================== */
  821. /* The dashboard's hero composer: the page's FIRST and predominant
  822. control, above every dashboard section. */
  823. .advisor-hero {
  824. border-color: var(--spry-accent);
  825. margin-bottom: var(--spry-space-4);
  826. }
  827. .advisor-hero .notice { margin-bottom: 0; }
  828. /* The hero's own block form — deliberately NOT an inline cluster (it
  829. would squeeze the textarea) nor a capped .field (the 34rem column
  830. cap is for edit forms, not a hero): a full-width label/textarea/
  831. actions stack with a roomy, larger-type composer. */
  832. .advisor-hero-form {
  833. display: flex;
  834. flex-direction: column;
  835. gap: var(--spry-space-2);
  836. margin: 0 0 var(--spry-space-2);
  837. }
  838. .advisor-hero-form label {
  839. font-weight: 600;
  840. font-size: 0.95rem;
  841. }
  842. .advisor-hero-form textarea {
  843. width: 100%;
  844. min-height: 4.5rem;
  845. resize: vertical;
  846. font-size: 1.1rem;
  847. line-height: 1.45;
  848. }
  849. .advisor-hero-form .advisor-hero-actions {
  850. display: flex;
  851. justify-content: flex-end;
  852. }
  853. /* The chat page: a column of transcript lanes under the head. */
  854. .chat-page { display: flex; flex-direction: column; gap: var(--spry-space-3); }
  855. .chat-head h2 { margin-bottom: 0.25rem; }
  856. .chat-head .chat-delete { margin-left: var(--spry-space-2); }
  857. .chat-thread {
  858. background: var(--spry-surface);
  859. border: var(--spry-border-width) solid var(--spry-border);
  860. border-radius: var(--spry-radius);
  861. display: flex;
  862. flex-direction: column;
  863. gap: var(--spry-space-2);
  864. max-height: 60vh;
  865. overflow-y: auto;
  866. padding: var(--spry-space-3);
  867. }
  868. /* Bubbles: the user's rows hug the right, the advisor's the left. */
  869. .chat-row { display: flex; flex-direction: column; }
  870. .chat-row-user { align-items: flex-end; }
  871. .chat-row-assistant,
  872. .chat-row-system,
  873. .chat-row-error { align-items: flex-start; }
  874. .chat-bubble {
  875. border: var(--spry-border-width) solid var(--spry-border);
  876. border-radius: var(--spry-radius);
  877. max-width: 46rem;
  878. padding: var(--spry-space-2) var(--spry-space-3);
  879. }
  880. .chat-bubble .md,
  881. .chat-bubble .md p { margin: 0; }
  882. .chat-bubble .md p + p { margin-top: 0.5rem; }
  883. .chat-user { background: var(--spry-info-soft); border-color: var(--spry-info); }
  884. .chat-assistant { background: var(--spry-bg-alt); }
  885. .chat-bubble .muted { font-size: 0.75rem; margin: 0.25rem 0 0; }
  886. /* Tool calls sit between the lanes as collapsed details. */
  887. .chat-tool { width: 100%; }
  888. .chat-tool details {
  889. background: var(--spry-bg-alt);
  890. border: var(--spry-border-width) solid var(--spry-border);
  891. border-radius: var(--spry-radius-sm);
  892. font-size: 0.85rem;
  893. padding: 0.35rem 0.75rem;
  894. }
  895. .chat-tool summary { cursor: pointer; }
  896. .chat-tool .badge { margin-right: 0.5rem; }
  897. .chat-tool .muted { margin: 0.4rem 0 0.15rem; }
  898. .chat-tool pre {
  899. background: var(--spry-surface);
  900. border: var(--spry-border-width) solid var(--spry-border);
  901. border-radius: var(--spry-radius-sm);
  902. font-size: 0.78rem;
  903. margin: 0;
  904. max-height: 18rem;
  905. overflow: auto;
  906. padding: 0.5rem 0.6rem;
  907. white-space: pre-wrap;
  908. word-break: break-word;
  909. }
  910. /* Errors stand out; system notices stay quiet. */
  911. .chat-error { width: 100%; }
  912. .chat-error p {
  913. background: var(--spry-danger-soft);
  914. border: var(--spry-border-width) solid var(--spry-danger);
  915. border-radius: var(--spry-radius);
  916. color: var(--spry-danger);
  917. margin: 0;
  918. padding: var(--spry-space-2) var(--spry-space-3);
  919. }
  920. .chat-system { width: 100%; }
  921. .chat-system p {
  922. border-left: 3px solid var(--spry-border);
  923. color: var(--spry-fg-muted);
  924. margin: 0;
  925. padding-left: 0.75rem;
  926. }
  927. /* A parked question: the bubble plus its answer card. */
  928. .chat-question { align-items: flex-start; display: flex; flex-direction: column; }
  929. .chat-question-card { margin-top: var(--spry-space-2); padding: var(--spry-space-2) var(--spry-space-3); }
  930. .chat-question-card .muted { margin: 0 0 var(--spry-space-2); }
  931. .chat-options { display: flex; flex-wrap: wrap; gap: var(--spry-space-2); margin-bottom: var(--spry-space-2); }
  932. .chat-options button { cursor: pointer; }
  933. /* The thinking indicator, while a turn runs. */
  934. .chat-thinking p {
  935. animation: chat-think 1.2s ease-in-out infinite;
  936. font-style: italic;
  937. margin: 0;
  938. }
  939. @keyframes chat-think { 50% { opacity: 0.45; } }
  940. /* Suggestion cards: the decision buttons row. */
  941. .chat-suggestion-actions {
  942. display: flex;
  943. flex-wrap: wrap;
  944. gap: var(--spry-space-2);
  945. margin-top: var(--spry-space-2);
  946. }
  947. /* The composer: a textarea beside its send button. */
  948. .chat-composer-row {
  949. display: flex;
  950. gap: var(--spry-space-2);
  951. align-items: flex-end;
  952. }
  953. .chat-composer-row textarea { flex: 1; resize: vertical; }
  954. .chat-composer-row button:disabled,
  955. .chat-composer-row textarea:disabled { opacity: 0.6; }
  956. @media (max-width: 40rem) {
  957. .cards { grid-template-columns: 1fr; }
  958. .grid-2 { grid-template-columns: 1fr; }
  959. .chat-bubble { max-width: 100%; }
  960. .chat-composer-row { flex-direction: column; align-items: stretch; }
  961. }
  962. /* The yeehaw celebration row (T11: .yeehaw-card/.yeehaw-list deleted —
  963. the earned-yeehaw card surface died with the entry stream; the live
  964. celebration is the log's .yeehaw-message span, pinned by
  965. test_task_log_js.py). */
  966. .yeehaw-message { font-weight: 600; margin-right: var(--spry-space-2); }
  967. /* The saddle's SSH public key display: a quiet monospace panel. */
  968. .ssh-key {
  969. background: var(--spry-bg-inset);
  970. border: var(--spry-border-width) solid var(--spry-border);
  971. border-radius: var(--spry-radius-sm);
  972. color: var(--spry-fg-muted);
  973. font-size: 0.75rem;
  974. margin: 0.5em 0;
  975. max-height: 7em;
  976. overflow: auto;
  977. padding: 0.5em 0.75em;
  978. white-space: pre-wrap;
  979. word-break: break-all;
  980. }
  981. /* ==========================================================================
  982. Dashboard attention-first layout (goal 41 task 3)
  983. Page-specific rules for home.html ONLY — appended per the spry
  984. conversion contract; every colour/spacing value consumes a --spry-*
  985. token so themes and auto dark mode work with zero extra rules.
  986. ========================================================================== */
  987. /* The attention/rail split: Spry's .with-sidebar gives the rail a
  988. minmax(11rem, 16rem) first column; the dashboard wants the rail on
  989. the RIGHT and WIDER (metrics + saddle list + bell strip), the
  990. attention column taking the rest. */
  991. .dash-board {
  992. grid-template-columns: minmax(0, 1fr) minmax(14rem, 19rem);
  993. }
  994. /* app.css loads after spry.css, so the re-proportioned columns above
  995. would out-rank the framework's own 55rem stacking rule inside its
  996. @media block (media queries add no specificity). Restore the stack
  997. here so the rail drops under the attention column on narrow
  998. screens. */
  999. @media (max-width: 55rem) {
  1000. .dash-board { grid-template-columns: minmax(0, 1fr); }
  1001. }
  1002. /* Section headings inside the attention column: quieter than the
  1003. page's card headings so the pinned cards stay the visual lead. */
  1004. .dash-heading {
  1005. font-size: 1.15rem;
  1006. margin-bottom: var(--spry-space-2);
  1007. }
  1008. .dash-heading .badge {
  1009. margin-left: var(--spry-space-2);
  1010. vertical-align: middle;
  1011. }
  1012. /* The rail's metric lead line: the number reads larger than the
  1013. card's prose so the one-glance verdict lands first. */
  1014. .dash-metric {
  1015. font-size: 1.05rem;
  1016. margin: 0 0 var(--spry-space-1);
  1017. }
  1018. .dash-metric strong {
  1019. font-size: 1.6rem;
  1020. font-weight: 650;
  1021. line-height: 1;
  1022. margin-right: var(--spry-space-1);
  1023. }
  1024. /* The rail's saddle list: a compact status list (ux-notes §1 B), one
  1025. line per saddle — badge, name, current work — instead of the old
  1026. full card grid. */
  1027. .dash-saddles {
  1028. display: flex;
  1029. flex-direction: column;
  1030. gap: var(--spry-space-1);
  1031. list-style: none;
  1032. margin: 0 0 var(--spry-space-2);
  1033. max-width: 100%;
  1034. padding: 0;
  1035. }
  1036. .dash-saddles li {
  1037. align-items: baseline;
  1038. display: flex;
  1039. flex-wrap: wrap;
  1040. font-size: 0.85rem;
  1041. gap: 0 var(--spry-space-1);
  1042. }
  1043. .dash-saddles .badge {
  1044. font-size: 0.7rem;
  1045. margin-right: var(--spry-space-1);
  1046. }
  1047. /* Ambient zones (activity, notes, discoveries): reference material,
  1048. deliberately flat — the rules below keep Spry's expander chrome
  1049. from boxing them into card-weight sections that would compete with
  1050. the attention column above (the goal-41 direction review: collapsed
  1051. zones read as ruled rows, not cards). */
  1052. .dash-ambient > summary {
  1053. color: var(--spry-fg-muted);
  1054. font-size: 0.95rem;
  1055. font-weight: 600;
  1056. }
  1057. .dash-ambient > summary .badge {
  1058. font-size: 0.7rem;
  1059. font-weight: 400;
  1060. }
  1061. .dash-ambient-body {
  1062. border-left: 2px solid var(--spry-border);
  1063. margin: var(--spry-space-1) 0 var(--spry-space-3) var(--spry-space-2);
  1064. padding: 0 0 0 var(--spry-space-3);
  1065. }
  1066. /* Activity cap (ux-notes §1 C): eight rows visible until "Show all"
  1067. flips [data-activity-all]; the count badge still covers the whole
  1068. window the slot carries. T11: the two .dash-activity-table rules
  1069. are GONE — T10's work stream replaced the activity table surface
  1070. (the t10views bundle asserts its absence), so the cap rules were
  1071. dead selectors. */
  1072. /*/* The advisor hero's in-card heading copy (the direction review's composer
  1073. shape): the description sits inside the composer card, above the
  1074. textarea; the chats link sits with the action, left. Same
  1075. specificity as the goal-27 rule above — this section is later in
  1076. the file, so it wins the tie without editing the earlier rule. */
  1077. .advisor-hero-lede {
  1078. margin: calc(-1 * var(--spry-space-1)) 0 0;
  1079. }
  1080. .advisor-hero-form .advisor-hero-actions {
  1081. align-items: baseline;
  1082. justify-content: space-between;
  1083. }
  1084. .advisor-hero-form .advisor-hero-actions .advisor-hero-link {
  1085. margin: 0;
  1086. }
  1087. /* ==========================================================================
  1088. Goal 41 T4 — tasks/goals/projects/stacks list pages (append-only)
  1089. The faceted status chips reuse .goal-strip/.goal-filter above; this
  1090. section only adds what the pages needed that no layer owned.
  1091. ========================================================================== */
  1092. /* A page head with its primary action: the h1/h2 on the left, the
  1093. "+ New …" button on the right, wrapping under on narrow screens
  1094. (.cluster gives the wrap + gap; only the space-between is ours). */
  1095. .cluster-between { justify-content: space-between; }
  1096. /* The projects list's folded "repo" cell (ux-notes projects list):
  1097. the clone URL truncated on one mono line with the branch under it.
  1098. .truncate needs a width bound inside a table cell to bite. */
  1099. td .truncate.repo-url {
  1100. display: block;
  1101. max-width: 22rem;
  1102. }
  1103. /* ==========================================================================
  1104. Goal 41 T5 — task detail Act|Consult layout, anchor sub-nav, sticky
  1105. log toolbar (append-only section per the spry-conversion contract;
  1106. every value consumes a --spry-* token so themes and auto dark mode
  1107. need zero extra rules).
  1108. ========================================================================== */
  1109. /* The Act|Consult split (T11: ONE class for both detail pages — the
  1110. task page's .task-columns and the goal page's .goal-columns were
  1111. the same rule with drifting rail widths, now unified): Spry's
  1112. .with-sidebar gives the rail a minmax(11rem, 16rem) FIRST column;
  1113. the detail pages want the acting column WIDE (the blocks and
  1114. comments are the page's work) and the consult rail bounded. Same
  1115. override shape as T3's .dash-board. */
  1116. .act-consult {
  1117. grid-template-columns: minmax(0, 1fr) minmax(18rem, 24rem);
  1118. }
  1119. /* app.css loads after spry.css, so the re-proportioned columns above
  1120. would out-rank the framework's own 55rem stacking rule inside its
  1121. @media block (media queries add no specificity). Restore the stack
  1122. here so Consult drops under Act on narrow screens — Act leads. */
  1123. @media (max-width: 55rem) {
  1124. .act-consult { grid-template-columns: minmax(0, 1fr); }
  1125. }
  1126. /* The anchor sub-nav (ux-notes §5 C, T11 unified): a horizontal strip
  1127. of in-page links, sticky UNDER the app topbar. T5 shipped the pill
  1128. language, T6 an underline variant under .goal-subnav; the unified
  1129. engine + class keep ONE strip vocabulary. The strip is quiet page
  1130. furniture until the engine flags it .is-stuck (the opaque surface
  1131. and edge arrive with stickiness); the active link reads as the
  1132. current section through the accent pill. */
  1133. .subnav {
  1134. display: flex;
  1135. gap: var(--spry-space-1);
  1136. margin: var(--spry-space-3) 0;
  1137. overflow-x: auto;
  1138. padding: var(--spry-space-1) 0;
  1139. scrollbar-width: none;
  1140. }
  1141. .subnav::-webkit-scrollbar { display: none; }
  1142. .subnav a {
  1143. border: var(--spry-border-width) solid transparent;
  1144. border-radius: var(--spry-radius-pill);
  1145. color: var(--spry-fg-muted);
  1146. font-size: 0.88rem;
  1147. padding: 0.18rem 0.75rem;
  1148. text-decoration: none;
  1149. white-space: nowrap;
  1150. }
  1151. .subnav a:hover {
  1152. background: var(--spry-bg-alt);
  1153. color: var(--spry-fg);
  1154. }
  1155. .subnav a.active {
  1156. background: var(--spry-accent-soft);
  1157. border-color: var(--spry-accent);
  1158. color: var(--spry-accent);
  1159. }
  1160. .subnav.is-stuck {
  1161. background: var(--spry-surface);
  1162. border-bottom: var(--spry-border-width) solid var(--spry-border);
  1163. box-shadow: var(--spry-shadow);
  1164. margin: 0 0 var(--spry-space-3);
  1165. position: sticky;
  1166. top: var(--topbar-bottom, 3.1rem);
  1167. z-index: 10;
  1168. }
  1169. /* Anchor targets clear the sticky chrome (the strip + topbar) so a
  1170. sub-nav jump lands the heading below them, not behind. */
  1171. [data-subnav-section] { scroll-margin-top: 6.5rem; }
  1172. /* The config cluster's summary count line: the numbers read at a
  1173. glance while the region stays collapsed (ux-notes task detail C). */
  1174. .config-history summary { cursor: pointer; }
  1175. .config-history h4 {
  1176. font-size: 1rem;
  1177. margin-bottom: var(--spry-space-1);
  1178. }
  1179. /* The log page's sticky toolbar (ux-notes event log A): the shell is
  1180. a plain block; making it a column flex keeps the toolbar glued to
  1181. the shell's top edge while the FEED keeps its own internal scroll
  1182. (its max-height contract from the log engine is untouched). The
  1183. jump-to-latest button inherits the filter pill language. */
  1184. .log-shell { display: flex; flex-direction: column; }
  1185. .log-toolbar {
  1186. position: sticky;
  1187. top: var(--topbar-bottom, 3.1rem);
  1188. z-index: 5;
  1189. }
  1190. .log-jump {
  1191. background: var(--spry-accent-soft);
  1192. border: var(--spry-border-width) solid var(--spry-accent);
  1193. border-radius: var(--spry-radius-pill);
  1194. color: var(--spry-accent);
  1195. font-size: 0.8rem;
  1196. padding: 0.25rem 0.8rem;
  1197. white-space: nowrap;
  1198. }
  1199. .log-jump:hover { background: var(--spry-accent); color: var(--spry-accent-fg); }
  1200. /* ==========================================================================
  1201. Goal 41 T6 — goal detail: state strip + sub-nav + Act|Consult
  1202. (goal_detail.html only; appended per the spry conversion contract;
  1203. every colour/spacing value consumes a --spry-* token).
  1204. ========================================================================== */
  1205. /* The leading review cards (goal 41 T6 "actions surface"): a pending
  1206. plan or completion review renders as its own card at the TOP of
  1207. the acting column, accented like the status head so the open
  1208. decision reads before anything else. */
  1209. .plan-review-card { border-left: 4px solid var(--spry-accent); }
  1210. /* The one-line state strip (ux-notes §5 A): status badge, the
  1211. open-item verdict, the primary lifecycle button — one glance
  1212. before anything else on the page. A quiet accent-tinted panel so
  1213. it reads as the page's verdict line, not another card. */
  1214. .goal-state-strip {
  1215. align-items: center;
  1216. background: var(--spry-accent-soft);
  1217. border: var(--spry-border-width) solid var(--spry-accent);
  1218. border-left: 4px solid var(--spry-accent);
  1219. border-radius: var(--spry-radius);
  1220. display: flex;
  1221. flex-wrap: wrap;
  1222. gap: var(--spry-space-2) var(--spry-space-3);
  1223. margin-bottom: var(--spry-space-3);
  1224. padding: var(--spry-space-2) var(--spry-space-3);
  1225. }
  1226. .goal-state-verdict { flex: 1 1 12rem; min-width: 0; }
  1227. /* The stacked pieces of a compound verdict ("plan revision pending"
  1228. + "1 question waiting") read with a middot, like the status head. */
  1229. .goal-state-verdict > span + span::before { content: ' · '; }
  1230. /* T11: the goal page's sub-nav strip + Act|Consult columns adopted
  1231. the shared engine — the strip is the unified .subnav (the engine's
  1232. .is-stuck class now owns stickiness, replacing the always-sticky
  1233. .goal-subnav) and the columns are the shared .act-consult. The
  1234. per-page variants (.goal-subnav, .goal-columns, .task-columns)
  1235. were deleted in this sweep. */
  1236. /* ==========================================================================
  1237. Goal 41 T7 — task page <-> event log unification (append-only section
  1238. per the spry-conversion contract; every value consumes a --spry-* token
  1239. so themes and auto dark mode need zero extra rules).
  1240. ========================================================================== */
  1241. /* The task page's Overview|Log tabs: Spry's .tabs already owns the
  1242. radio/label/panel mechanics; the task page just widens the strip's
  1243. presence a touch (the tab row sits directly under the breadcrumb,
  1244. above the sub-nav it now frames). */
  1245. .task-tabs {
  1246. margin-top: var(--spry-space-2);
  1247. }
  1248. /* The Progress cluster's "Event log" control is a <label> for the log
  1249. radio, not an anchor: clicking it flips the page into the log view
  1250. in place (ux-notes structural bet 3 - no navigation, no new tab).
  1251. It borrows the quiet link language so it reads as the toggle it
  1252. is, not a form control. */
  1253. .tab-link {
  1254. color: var(--spry-accent);
  1255. cursor: pointer;
  1256. font-size: 0.95rem;
  1257. text-decoration: underline;
  1258. text-decoration-color: var(--spry-border);
  1259. text-underline-offset: 0.2em;
  1260. }
  1261. .tab-link:hover { text-decoration-color: var(--spry-accent); }
  1262. /* The embedded log panel keeps the standalone page's rhythm: the
  1263. context line + live badge cluster above the shell, and the shell's
  1264. own sticky toolbar does its work inside the panel. The feed's
  1265. max-height contract (72vh, its own scroller) is untouched - deep
  1266. history stays navigable without scrolling the whole page. */
  1267. .task-log-panel .log-shell { margin-top: var(--spry-space-2); }
  1268. .task-log-panel .log-toolbar {
  1269. /* Inside the tab panel the toolbar parks under the topbar like
  1270. the standalone page; the subnav strip is Overview-only. */
  1271. top: var(--topbar-bottom, 3.1rem);
  1272. }
  1273. /* The sub-nav strip belongs to the Overview surface; the Log panel
  1274. frames itself with the context cluster instead. Keep the strip's
  1275. spacing from leaking into the log panel's top margin. */
  1276. .task-overview-panel > .subnav { margin-top: 0; }
  1277. /* ==========================================================================
  1278. Goal 41 T8 — saddles head/tail + fleet admin pages
  1279. (saddles/users/model_settings/stack_detail; appended per the spry
  1280. conversion contract; every value consumes a --spry-* token).
  1281. ========================================================================== */
  1282. /* The saddle card's "now" head (ux-notes §6 A): the verdict line is
  1283. the anchor of the fold — a hairline under the first ~3 lines
  1284. separates what the saddle IS doing from the keys & admin tail
  1285. that folds. The .cards grid keeps its page-level gutters; this
  1286. only draws the fold inside each card. */
  1287. .saddle-fold {
  1288. border-top: var(--spry-border-width) solid var(--spry-border);
  1289. margin-top: var(--spry-space-2);
  1290. padding-top: var(--spry-space-2);
  1291. }
  1292. /* The admin tail's inner stack sits inside the summary, so it gets
  1293. the card's inset surface to read as one folding block. */
  1294. .saddle-admin { padding-left: var(--spry-space-2); }
  1295. /* ==========================================================================
  1296. Goal 41 T9 — library pages (project_detail / rules / discoveries / chats)
  1297. No component additions: every surface adopts Spry primitives
  1298. (.card-header/-body, dialogs, .btn-ghost, .alert-*) and the shared
  1299. data-long-form engine, per the spry-conversion contract. This
  1300. marker section exists so parallel goal-41 tasks can merge by
  1301. keeping both sides in sequence.
  1302. ========================================================================== */
  1303. /* ==========================================================================
  1304. Goal 41 T10 — saved views on /tasks + the dashboard work stream
  1305. (appended per the spry-conversion contract; every value consumes a
  1306. --spry-* token, so themes and auto dark mode ride along).
  1307. ========================================================================== */
  1308. /* The task queue's view row (ux-notes bet 4): the preset chips reuse
  1309. the goal-strip chip language (they ARE status chips - one click and
  1310. one shareable URL each), the copy-link button sits at the row's end
  1311. behind a hairline. The separator is a vertical hairline, not a
  1312. character, so it never fights the labels. */
  1313. .task-view-row {
  1314. align-items: center;
  1315. row-gap: var(--spry-space-1);
  1316. }
  1317. .task-view-sep {
  1318. align-self: stretch;
  1319. background: var(--spry-border);
  1320. display: inline-block;
  1321. margin-left: var(--spry-space-1);
  1322. width: var(--spry-border-width);
  1323. }
  1324. /* The dashboard work stream (ux-notes bet 5): one compact feed that
  1325. joins task progress, goal narrative and log highlights. Rows are
  1326. list items, not a table: a stream is read down the left edge —
  1327. badge, when, actor link, text — and wraps under itself when narrow,
  1328. where the old three-column table could not. */
  1329. .work-stream {
  1330. display: grid;
  1331. gap: var(--spry-space-1);
  1332. list-style: none;
  1333. margin: 0;
  1334. padding: 0;
  1335. }
  1336. .work-stream li {
  1337. align-items: baseline;
  1338. display: flex;
  1339. flex-wrap: wrap;
  1340. gap: var(--spry-space-2);
  1341. }
  1342. .work-stream-when {
  1343. flex: none;
  1344. font-size: 0.85em;
  1345. min-width: 4.5em;
  1346. }
  1347. .work-stream-text {
  1348. display: inline;
  1349. min-width: 0;
  1350. }
  1351. .work-stream-actor {
  1352. margin-right: var(--spry-space-1);
  1353. }
  1354. /* The stream's three row grammars (one badge vocabulary, each mapped
  1355. to its semantic group per the badge contract): task progress is the
  1356. fleet's output (the success group, like .badge-progress), goal
  1357. events are the narrative (accent, like .badge-kind-goal), log
  1358. highlights are the quiet remainder (inset, like comment/tool). */
  1359. .badge-stream-task_progress {
  1360. background: var(--spry-success-soft);
  1361. color: var(--spry-success);
  1362. }
  1363. .badge-stream-goal_event {
  1364. background: var(--spry-accent-soft);
  1365. color: var(--spry-accent);
  1366. }
  1367. .badge-stream-log_highlight {
  1368. background: var(--spry-bg-inset);
  1369. color: var(--spry-fg-muted);
  1370. }
  1371. /* ==========================================================================
  1372. Goal 41 review fix-ups (post-T11 review feedback)
  1373. Two integration defects the review caught: cards had no breathing room
  1374. (spry's .card carries no padding of its own - padding belongs to
  1375. .card-header/-body/-footer, and this app puts content directly in .card),
  1376. and the account menu used .menu, which is spry's ALWAYS-EXPANDED
  1377. vertical menu variant - it forced the submenu open and bulky. Cards now
  1378. carry the framework's own --spry-card-padding; the account menu uses
  1379. .nav like every other dropdown (markup fix in main.html).
  1380. ========================================================================== */
  1381. /* Default card padding: the framework's own card rhythm. Cards built
  1382. from the header/body/footer children keep the framework's chrome
  1383. (those children self-pad the same amount, so the parent's padding is
  1384. removed for them); every other card gets the padding directly.
  1385. Chosen over negative-margin bleed after a headless-browser probe
  1386. showed margins break the header/footer full-width chrome. */
  1387. .card {
  1388. padding: var(--spry-card-padding);
  1389. }
  1390. .card:has(> .card-header),
  1391. .card:has(> .card-body),
  1392. .card:has(> .card-footer) {
  1393. padding: 0;
  1394. }
  1395. /* The loading state (review round 2): a centred Spry spinner instead of
  1396. a "Loading..." line - both the shared preloader in main.html and the
  1397. per-page stm-if placeholders while a slot is null. min-height keeps
  1398. the box tall enough that the centre is visibly mid-page, not mid-
  1399. strip. */
  1400. .page-loading {
  1401. align-items: center;
  1402. display: flex;
  1403. justify-content: center;
  1404. min-height: 40vh;
  1405. }