loopcaptain 0.3.1__py3-none-any.whl

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 (50) hide show
  1. loopcaptain/__init__.py +0 -0
  2. loopcaptain/agents.py +162 -0
  3. loopcaptain/board/AGENTS.md +39 -0
  4. loopcaptain/board/__init__.py +0 -0
  5. loopcaptain/board/app.py +430 -0
  6. loopcaptain/board/discover.py +249 -0
  7. loopcaptain/board/files.py +94 -0
  8. loopcaptain/board/filters.py +48 -0
  9. loopcaptain/board/graph.py +139 -0
  10. loopcaptain/board/overview.py +59 -0
  11. loopcaptain/board/static/board.js +41 -0
  12. loopcaptain/board/static/fonts/OFL.txt +94 -0
  13. loopcaptain/board/static/fonts/inter-latin-wght-normal.woff2 +0 -0
  14. loopcaptain/board/static/fonts/inter-tight-latin-wght-normal.woff2 +0 -0
  15. loopcaptain/board/static/mark.svg +5 -0
  16. loopcaptain/board/static/style.css +425 -0
  17. loopcaptain/board/steer.py +207 -0
  18. loopcaptain/board/templates/base.html +27 -0
  19. loopcaptain/board/templates/connect.html +35 -0
  20. loopcaptain/board/templates/file.html +9 -0
  21. loopcaptain/board/templates/loop.html +357 -0
  22. loopcaptain/board/templates/overview.html +108 -0
  23. loopcaptain/board/templates/unreadable.html +17 -0
  24. loopcaptain/board/trend.py +185 -0
  25. loopcaptain/cli.py +204 -0
  26. loopcaptain/demo/__init__.py +59 -0
  27. loopcaptain/demo/loop.py +311 -0
  28. loopcaptain/demo/loops.py +76 -0
  29. loopcaptain/mcp_server/AGENTS.md +23 -0
  30. loopcaptain/mcp_server/__init__.py +0 -0
  31. loopcaptain/mcp_server/server.py +181 -0
  32. loopcaptain/prompt_texts/build-a-loop.md +6 -0
  33. loopcaptain/prompt_texts/watch-my-loops.md +6 -0
  34. loopcaptain/prompts.py +27 -0
  35. loopcaptain/protocol/AGENTS.md +24 -0
  36. loopcaptain/protocol/LOOPS.md +402 -0
  37. loopcaptain/protocol/__init__.py +17 -0
  38. loopcaptain/protocol/doctor.py +79 -0
  39. loopcaptain/protocol/models.py +496 -0
  40. loopcaptain/protocol/signing.py +105 -0
  41. loopcaptain/protocol/state.py +595 -0
  42. loopcaptain/protocol/store.py +275 -0
  43. loopcaptain/protocol/validate.py +199 -0
  44. loopcaptain-0.3.1.dist-info/METADATA +220 -0
  45. loopcaptain-0.3.1.dist-info/RECORD +50 -0
  46. loopcaptain-0.3.1.dist-info/WHEEL +4 -0
  47. loopcaptain-0.3.1.dist-info/entry_points.txt +2 -0
  48. loopcaptain-0.3.1.dist-info/licenses/LICENSE +202 -0
  49. loopcaptain-0.3.1.dist-info/licenses/NOTICE +11 -0
  50. loopcaptain-0.3.1.dist-info/licenses/src/loopcaptain/board/static/fonts/OFL.txt +94 -0
@@ -0,0 +1,94 @@
1
+ Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter)
2
+ Copyright 2022 The Inter Project Authors (https://github.com/rsms/inter-tight)
3
+
4
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
5
+ This license is copied below, and is also available with a FAQ at:
6
+ http://scripts.sil.org/OFL
7
+
8
+
9
+ -----------------------------------------------------------
10
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11
+ -----------------------------------------------------------
12
+
13
+ PREAMBLE
14
+ The goals of the Open Font License (OFL) are to stimulate worldwide
15
+ development of collaborative font projects, to support the font creation
16
+ efforts of academic and linguistic communities, and to provide a free and
17
+ open framework in which fonts may be shared and improved in partnership
18
+ with others.
19
+
20
+ The OFL allows the licensed fonts to be used, studied, modified and
21
+ redistributed freely as long as they are not sold by themselves. The
22
+ fonts, including any derivative works, can be bundled, embedded,
23
+ redistributed and/or sold with any software provided that any reserved
24
+ names are not used by derivative works. The fonts and derivatives,
25
+ however, cannot be released under any other type of license. The
26
+ requirement for fonts to remain under this license does not apply
27
+ to any document created using the fonts or their derivatives.
28
+
29
+ DEFINITIONS
30
+ "Font Software" refers to the set of files released by the Copyright
31
+ Holder(s) under this license and clearly marked as such. This may
32
+ include source files, build scripts and documentation.
33
+
34
+ "Reserved Font Name" refers to any names specified as such after the
35
+ copyright statement(s).
36
+
37
+ "Original Version" refers to the collection of Font Software components as
38
+ distributed by the Copyright Holder(s).
39
+
40
+ "Modified Version" refers to any derivative made by adding to, deleting,
41
+ or substituting -- in part or in whole -- any of the components of the
42
+ Original Version, by changing formats or by porting the Font Software to a
43
+ new environment.
44
+
45
+ "Author" refers to any designer, engineer, programmer, technical
46
+ writer or other person who contributed to the Font Software.
47
+
48
+ PERMISSION & CONDITIONS
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
51
+ redistribute, and sell modified and unmodified copies of the Font
52
+ Software, subject to the following conditions:
53
+
54
+ 1) Neither the Font Software nor any of its individual components,
55
+ in Original or Modified Versions, may be sold by itself.
56
+
57
+ 2) Original or Modified Versions of the Font Software may be bundled,
58
+ redistributed and/or sold with any software, provided that each copy
59
+ contains the above copyright notice and this license. These can be
60
+ included either as stand-alone text files, human-readable headers or
61
+ in the appropriate machine-readable metadata fields within text or
62
+ binary files as long as those fields can be easily viewed by the user.
63
+
64
+ 3) No Modified Version of the Font Software may use the Reserved Font
65
+ Name(s) unless explicit written permission is granted by the corresponding
66
+ Copyright Holder. This restriction only applies to the primary font name as
67
+ presented to the users.
68
+
69
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70
+ Software shall not be used to promote, endorse or advertise any
71
+ Modified Version, except to acknowledge the contribution(s) of the
72
+ Copyright Holder(s) and the Author(s) or with their explicit written
73
+ permission.
74
+
75
+ 5) The Font Software, modified or unmodified, in part or in whole,
76
+ must be distributed entirely under this license, and must not be
77
+ distributed under any other license. The requirement for fonts to
78
+ remain under this license does not apply to any document created
79
+ using the Font Software.
80
+
81
+ TERMINATION
82
+ This license becomes null and void if any of the above conditions are
83
+ not met.
84
+
85
+ DISCLAIMER
86
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Loopcaptain">
2
+ <rect width="64" height="64" rx="15" fill="#4A3FE0"/>
3
+ <path d="M47.45 27.86A16 16 0 1 1 36.14 16.55" fill="none" stroke="#FFFFFF" stroke-width="7" stroke-linecap="round"/>
4
+ <circle cx="44.2" cy="19.8" r="4.1" fill="#FFFFFF"/>
5
+ </svg>
@@ -0,0 +1,425 @@
1
+ /* Design tokens. Calm and quiet, so the one thing that needs the person stands out: a warm light
2
+ canvas (true black in dark mode), soft surfaces with a lit top edge, one action colour (iris:
3
+ links, the answer button, "waiting for you"), Inter Tight for headings and figures over Inter
4
+ for text. Semantic colours only for status, and every status also differs in shape, so the
5
+ board reads without colour. Open findings are an ink ramp, not a status colour: more ink, more
6
+ to do. 4/8-point spacing; type scale 12/14/16/20/26/36. Colours are RGB triplets so a tint is
7
+ rgb(var(--rgb-ink) / .04). Dark mode redefines the tokens and nothing else. Fonts are served
8
+ from this folder (SIL Open Font License, fonts/OFL.txt): the board fetches nothing. */
9
+
10
+ @font-face { font-family: "Inter"; font-style: normal; font-display: swap; font-weight: 100 900;
11
+ src: url("fonts/inter-latin-wght-normal.woff2") format("woff2-variations"); }
12
+ @font-face { font-family: "Inter Tight"; font-style: normal; font-display: swap; font-weight: 100 900;
13
+ src: url("fonts/inter-tight-latin-wght-normal.woff2") format("woff2-variations"); }
14
+
15
+ :root {
16
+ --s1: 8px; --s2: 16px; --s3: 24px; --s4: 32px; --s5: 48px; --s6: 72px;
17
+ --t12: 12px; --t14: 14px; --t16: 16px; --t20: 20px; --t26: 26px; --t36: 36px;
18
+ --radius: 10px; --radius-l: 18px; --pill: 999px; --target: 44px;
19
+ --font-display: "Inter Tight", "Inter", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
20
+ --font: "Inter", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
21
+ --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
22
+ --rgb-paper: 246 245 242; --rgb-surface: 255 255 255; --rgb-ink: 29 29 31; --rgb-grey: 100 100 105;
23
+ --rgb-accent: 74 63 224;
24
+ --bg: rgb(var(--rgb-paper)); --surface: rgb(var(--rgb-surface));
25
+ --text: rgb(var(--rgb-ink)); --text-2: rgb(var(--rgb-grey));
26
+ --tint: rgb(var(--rgb-ink) / .035); --tint-2: rgb(var(--rgb-ink) / .07); --line: rgb(var(--rgb-ink) / .09);
27
+ --accent: rgb(var(--rgb-accent)); --on-accent: #FFFFFF; --accent-tint: rgb(var(--rgb-accent) / .08);
28
+ --accent-line: rgb(var(--rgb-accent) / .28);
29
+ /* status: text colour (4.5:1 on its tint and on the canvas), tint, marker */
30
+ --ok: #0E6B3D; --ok-bg: rgb(19 128 75 / .12); --ok-mark: #13804B;
31
+ --run: #0B5F7A; --run-bg: rgb(11 114 150 / .12); --run-mark: #0B7296;
32
+ --warn: #8A5200; --warn-bg: rgb(184 110 0 / .13); --warn-mark: #B86E00;
33
+ --bad: #B00E28; --bad-bg: rgb(200 16 46 / .10); --bad-mark: #C8102E;
34
+ --idle: #55565C; --idle-bg: rgb(var(--rgb-ink) / .06); --idle-mark: #8A8B92;
35
+ /* open findings: one ink ramp, four steps */
36
+ --heat-1: rgb(var(--rgb-ink) / .10); --heat-2: rgb(var(--rgb-ink) / .22); --heat-3: rgb(var(--rgb-ink) / .55); --heat-4: rgb(var(--rgb-ink) / .92);
37
+ --on-heat: rgb(var(--rgb-paper));
38
+ --edge: inset 0 1px 0 rgb(255 255 255 / .85), inset 0 0 0 1px rgb(var(--rgb-ink) / .06);
39
+ --shadow: var(--edge), 0 1px 2px rgb(var(--rgb-ink) / .04), 0 12px 32px -18px rgb(var(--rgb-ink) / .22);
40
+ --ring: 0 0 0 3px rgb(var(--rgb-accent) / .28);
41
+ --ease: cubic-bezier(.2, .7, .2, 1);
42
+ color-scheme: light;
43
+ }
44
+ @media (prefers-color-scheme: dark) {
45
+ :root {
46
+ --rgb-paper: 0 0 0; --rgb-surface: 28 28 30; --rgb-ink: 245 245 247; --rgb-grey: 161 161 166;
47
+ --rgb-accent: 168 161 255; --on-accent: #0E0A3A;
48
+ --line: rgb(255 255 255 / .10);
49
+ --ok: #6FD09A; --ok-bg: rgb(111 208 154 / .14); --ok-mark: #4FBF82;
50
+ --run: #7CCBE6; --run-bg: rgb(124 203 230 / .14); --run-mark: #5BB8D8;
51
+ --warn: #EBB25E; --warn-bg: rgb(235 178 94 / .15); --warn-mark: #E0A040;
52
+ --bad: #FF8A94; --bad-bg: rgb(255 138 148 / .14); --bad-mark: #F06272;
53
+ --idle: #B7B8BF; --idle-bg: rgb(238 238 240 / .08); --idle-mark: #8E8F96;
54
+ --edge: inset 0 1px 0 rgb(255 255 255 / .07), inset 0 0 0 1px rgb(255 255 255 / .06);
55
+ --shadow: var(--edge), 0 12px 32px -16px rgb(0 0 0 / .7);
56
+ color-scheme: dark;
57
+ }
58
+ }
59
+
60
+ * { box-sizing: border-box; }
61
+ html { -webkit-text-size-adjust: 100%; }
62
+ body { margin: 0; min-height: 100vh; display: flex; flex-direction: column; background: var(--bg); color: var(--text);
63
+ font: var(--t16)/1.55 var(--font); font-feature-settings: "cv11", "ss01"; -webkit-font-smoothing: antialiased; }
64
+ a { color: var(--accent); text-decoration: none; text-underline-offset: 3px; }
65
+ a:hover { text-decoration: underline; }
66
+ ::selection { background: rgb(var(--rgb-accent) / .25); }
67
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
68
+ h1, h2, h3 { font-family: var(--font-display); margin: 0; line-height: 1.15; font-weight: 600; letter-spacing: -.02em; text-wrap: balance; overflow-wrap: break-word; }
69
+ h1 { font-size: var(--t36); font-weight: 700; letter-spacing: -.025em; line-height: 1.08; margin: var(--s1) 0 var(--s2); max-width: 30ch; }
70
+ h2 { font-size: var(--t20); margin: var(--s5) 0 var(--s2); }
71
+ nav.tabs ~ h2:first-of-type { margin-top: var(--s4); }
72
+ h2 small { font: 400 var(--t14)/1.4 var(--font); color: var(--text-2); letter-spacing: 0; margin-left: var(--s1); }
73
+ h3 { font-size: var(--t16); margin: 0 0 4px; }
74
+ p { margin: 0; }
75
+ code, pre, .mono { font-family: var(--mono); font-size: 13px; }
76
+ code { background: var(--tint-2); padding: 1px 6px; border-radius: 6px; }
77
+ .eyebrow { font-size: var(--t12); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-2); }
78
+ .grey { color: var(--text-2); }
79
+ .num { font-variant-numeric: tabular-nums; white-space: nowrap; }
80
+ .sr { position: absolute; left: 0; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
81
+
82
+ /* ---- header ---- */
83
+ .top { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: var(--s2); padding: 10px var(--s4);
84
+ background: rgb(var(--rgb-paper) / .82); -webkit-backdrop-filter: saturate(1.7) blur(22px); backdrop-filter: saturate(1.7) blur(22px); border-bottom: 1px solid var(--line); }
85
+ .brand { display: inline-flex; align-items: center; gap: 10px; min-height: var(--target); color: var(--text); font: 700 18px/1 var(--font-display); letter-spacing: -.03em; }
86
+ .brand:hover { text-decoration: none; }
87
+ .brand img { width: 28px; height: 28px; display: block; border-radius: 7px; }
88
+ .top .where { color: var(--text-2); font-size: var(--t14); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
89
+ nav.strip { margin-left: auto; display: flex; gap: 2px; padding: 3px; border-radius: var(--pill); background: var(--tint-2); }
90
+ nav.strip a { display: inline-flex; align-items: center; min-height: 36px; padding: 0 14px; border-radius: var(--pill); color: var(--text-2); font-size: var(--t14); font-weight: 500; white-space: nowrap; transition: background .2s var(--ease), color .2s var(--ease); }
91
+ nav.strip a:hover { color: var(--text); text-decoration: none; }
92
+ nav.strip a[aria-current="page"] { background: var(--surface); color: var(--text); font-weight: 600; box-shadow: var(--shadow); }
93
+
94
+ main { width: 100%; max-width: 1280px; margin: 0 auto; padding: var(--s4) var(--s4) var(--s6); flex: 1; }
95
+ .foot { width: 100%; max-width: 1280px; margin: 0 auto; padding: var(--s3) var(--s4) var(--s5); color: var(--text-2); font-size: var(--t12); border-top: 1px solid var(--line); }
96
+ .crumb { font-size: var(--t14); color: var(--text-2); }
97
+ .crumb a { color: var(--text-2); }
98
+ .lede { color: var(--text-2); font-size: var(--t20); line-height: 1.45; max-width: 62ch; letter-spacing: -.01em; }
99
+ .meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) 12px; color: var(--text-2); font-size: var(--t14); }
100
+ .note { color: var(--text-2); font-size: var(--t14); margin-top: var(--s1); max-width: 100ch; }
101
+ .empty { color: var(--text-2); border: 1px dashed var(--line); padding: var(--s3); border-radius: var(--radius-l); max-width: 80ch; }
102
+ .card { background: var(--surface); border-radius: var(--radius-l); box-shadow: var(--shadow); padding: var(--s3); min-width: 0; }
103
+ .card.flush { padding: 0; overflow-x: auto; }
104
+ .card.fit { width: fit-content; max-width: 100%; }
105
+
106
+ /* ---- status: colour and shape ---- */
107
+ .pill { display: inline-flex; align-items: center; gap: 7px; min-height: 26px; padding: 2px 11px 2px 9px; border-radius: var(--pill);
108
+ font-size: 13px; font-weight: 600; white-space: nowrap; background: var(--idle-bg); color: var(--idle); }
109
+ .pill::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px currentColor; }
110
+ .pill.waiting { background: var(--accent); color: var(--on-accent); } .pill.waiting::before { background: currentColor; }
111
+ .pill.running { background: var(--run-bg); color: var(--run); } .pill.running::before { background: var(--run-mark); box-shadow: none; animation: beat 1.6s var(--ease) infinite; }
112
+ .pill.done { background: var(--ok-bg); color: var(--ok); } .pill.done::before { background: var(--ok-mark); box-shadow: none; }
113
+ .pill.failed { background: var(--bad-bg); color: var(--bad); } .pill.failed::before { background: var(--bad-mark); box-shadow: none; border-radius: 0; height: 2.5px; width: 9px; }
114
+ .pill.stalled, .pill.capped { background: var(--warn-bg); color: var(--warn); } .pill.stalled::before, .pill.capped::before { background: var(--warn-mark); box-shadow: none; border-radius: 1.5px; }
115
+ .pill.stopped::before { border-radius: 1.5px; }
116
+ /* Sleeping: a half moon, the loop said when it is back. */
117
+ .pill.sleeping { background: var(--run-bg); color: var(--run); } .pill.sleeping::before { box-shadow: inset -3px 0 0 0 currentColor; }
118
+ @keyframes beat { 50% { opacity: .3; } }
119
+
120
+ /* ---- the parts of one loop: tabs, each its own address ---- */
121
+ nav.tabs { display: flex; gap: var(--s1); margin: var(--s4) 0 0; box-shadow: inset 0 -1px 0 var(--line); overflow: auto hidden; }
122
+ nav.tabs a { display: inline-flex; align-items: center; gap: var(--s1); min-height: var(--target); padding: 0 var(--s2); border-bottom: 2px solid transparent;
123
+ color: var(--text-2); font-size: var(--t14); font-weight: 500; white-space: nowrap; transition: color .2s var(--ease), border-color .2s var(--ease); }
124
+ nav.tabs a:first-child { padding-left: 2px; }
125
+ nav.tabs a:hover { color: var(--text); text-decoration: none; }
126
+ nav.tabs a[aria-current="page"] { color: var(--text); font-weight: 600; border-bottom-color: var(--text); }
127
+ nav.tabs .n { min-width: 22px; padding: 1px 7px; border-radius: var(--pill); background: var(--tint-2); font-size: var(--t12); font-weight: 600; text-align: center; font-variant-numeric: tabular-nums; }
128
+ nav.tabs + h2, nav.tabs + .round-head { margin-top: var(--s4); }
129
+ nav.tabs .n.hot { background: var(--accent); color: var(--on-accent); }
130
+ nav.tabs .n.hot::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 5px; border-radius: 50%; background: currentColor; vertical-align: 1px; }
131
+ .card.tabbed, nav.tabs + .ask { margin-top: var(--s3); }
132
+ .empty.tabbed { margin-top: var(--s4); }
133
+
134
+ /* ---- key figures ---- */
135
+ .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); gap: 12px; margin-top: var(--s3); }
136
+ .kpi { padding: 18px 20px 16px; background: var(--surface); border-radius: var(--radius-l); box-shadow: var(--shadow); }
137
+ .kpi b { display: block; font: 700 var(--t36)/1 var(--font-display); letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
138
+ .kpi b sup { font-size: 15px; letter-spacing: 0; vertical-align: top; margin-left: 3px; color: var(--text-2); font-weight: 500; line-height: 1.5; }
139
+ .kpi span { display: block; color: var(--text-2); font-size: 13px; line-height: 1.4; margin-top: var(--s1); }
140
+ .kpi.hot { background: var(--accent-tint); box-shadow: inset 0 0 0 1px var(--accent-line); }
141
+ .kpi.hot b { color: var(--accent); }
142
+ /* the two other tiles that ask for a look: something runs, something is stuck */
143
+ .kpi.live b { color: var(--run); } .kpi.live b::after { content: ""; display: inline-block; width: 9px; height: 9px; margin-left: 10px; border-radius: 50%; background: var(--run-mark); vertical-align: 9px; animation: beat 1.6s var(--ease) infinite; }
144
+ .kpi.alarm { background: var(--bad-bg); box-shadow: inset 0 0 0 1px rgb(200 16 46 / .22); } .kpi.alarm b { color: var(--bad); }
145
+
146
+ /* ---- what waits for the person: the one accent surface ---- */
147
+ .ask { margin-top: var(--s3); padding: var(--s3); border-radius: var(--radius-l); background: var(--accent-tint); box-shadow: inset 0 0 0 1px var(--accent-line); }
148
+ .ask + .ask { margin-top: 12px; }
149
+ .ask .eyebrow { color: var(--accent); }
150
+ .ask .eyebrow a { text-decoration: underline; }
151
+ .ask p.q { margin: 6px 0 var(--s2); font: 600 var(--t20)/1.35 var(--font-display); letter-spacing: -.015em; max-width: 70ch; white-space: pre-line; }
152
+ /* On the first page a question says where it comes from: the project, then the loop. */
153
+ .ask dl.from { display: flex; flex-wrap: wrap; gap: 4px var(--s3); margin: 0; font-size: var(--t14); }
154
+ .ask dl.from div { display: flex; gap: var(--s1); align-items: baseline; min-width: 0; }
155
+ .ask dl.from dt { font-size: var(--t12); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-2); }
156
+ .ask dl.from dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
157
+ .ask dl.from dd a { text-decoration: underline; }
158
+ .ask dl.from + p.q { font-size: var(--t16); line-height: 1.5; font-family: var(--font); letter-spacing: 0; font-weight: 500; margin-top: var(--s1); }
159
+
160
+ form.say { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: end; }
161
+ textarea { width: 100%; min-height: 68px; padding: 12px 14px; font: var(--t16)/1.45 var(--font); color: var(--text); background: var(--surface);
162
+ border: 1px solid var(--line); border-radius: var(--radius); resize: vertical; transition: border-color .2s var(--ease), box-shadow .2s var(--ease); }
163
+ textarea::placeholder { color: var(--text-2); }
164
+ textarea:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); border-radius: var(--radius); }
165
+ button { display: inline-flex; align-items: center; justify-content: center; min-height: var(--target); padding: 0 20px; border-radius: var(--pill);
166
+ border: 1px solid transparent; background: var(--accent); color: var(--on-accent); font: 600 var(--t14)/1 var(--font); cursor: pointer; white-space: nowrap;
167
+ transition: background .2s var(--ease), box-shadow .2s var(--ease), transform .2s var(--ease); }
168
+ button:hover { box-shadow: 0 6px 16px -8px rgb(var(--rgb-accent) / .7); }
169
+ button:active { transform: scale(.98); }
170
+ button.quiet { background: var(--surface); color: var(--text); border-color: var(--line); box-shadow: none; }
171
+ button.quiet:hover { background: var(--tint-2); }
172
+ button.danger { background: transparent; color: var(--bad); border-color: currentColor; box-shadow: none; }
173
+ button.danger:hover { background: var(--bad-bg); }
174
+ button:disabled { opacity: .5; cursor: not-allowed; transform: none; }
175
+
176
+ .flash { margin-top: var(--s2); padding: 12px var(--s2); border-radius: var(--radius); font-size: var(--t14); background: var(--ok-bg); color: var(--ok); }
177
+ .flash.bad { background: var(--bad-bg); color: var(--bad); }
178
+
179
+ /* ---- steering ---- */
180
+ .steer { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); gap: 12px; margin-top: 12px; }
181
+ .steer .card > .eyebrow { margin-bottom: 12px; }
182
+ .steer .row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
183
+ .steer .row + .row { margin-top: 12px; }
184
+ .steer .row form { margin: 0; }
185
+ .steer form.start { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
186
+ .steer form.start p { flex-basis: 100%; margin: 0; font-size: var(--t14); }
187
+ .steer label.tick { display: flex; gap: 8px; align-items: center; font-size: var(--t14); }
188
+ .steer pre.log { margin: var(--s2) 0 0; padding: 12px 14px; max-height: 200px; overflow: auto; background: var(--tint); border-radius: var(--radius); font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
189
+ ul.plain { margin: var(--s2) 0 0; padding: 0; list-style: none; }
190
+ .card > .eyebrow + ul.plain { margin-top: 0; } .card > .eyebrow + ul.plain li:first-child { border-top: 0; }
191
+ ul.plain li { padding: 10px 0; border-top: 1px solid var(--line); display: flex; gap: 12px; align-items: baseline; font-size: var(--t14); }
192
+ ul.plain li .tag { flex: none; min-width: 13ch; font-size: var(--t12); font-weight: 600; color: var(--text-2); }
193
+ ul.plain li .body { white-space: pre-line; }
194
+
195
+ /* ---- brief ---- */
196
+ .brief { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: var(--s3); }
197
+ .brief p:not(.eyebrow) { margin-top: 6px; white-space: pre-line; }
198
+
199
+ /* ---- tables ---- */
200
+ table { border-collapse: collapse; width: 100%; font-size: var(--t14); }
201
+ th { text-align: left; font-size: var(--t12); font-weight: 600; color: var(--text-2); padding: 14px var(--s2); border-bottom: 1px solid var(--line); white-space: nowrap; }
202
+ td { padding: 14px var(--s2); border-bottom: 1px solid var(--line); vertical-align: top; }
203
+ tr:last-child td { border-bottom: 0; }
204
+ td.r, th.r { text-align: right; }
205
+ /* The whole row opens the loop: the name's link is stretched over it, and the last cell says so. */
206
+ table.loops tbody tr { position: relative; transition: background .2s var(--ease); }
207
+ table.loops td.name a::after { content: ""; position: absolute; inset: 0; }
208
+ table.loops td.name a:focus-visible { outline: none; } table.loops tbody tr:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }
209
+ table.loops td.go { color: var(--accent); font-weight: 600; white-space: nowrap; text-align: right; }
210
+ table.loops td.go i { display: inline-block; width: 7px; height: 7px; margin-left: 8px; border-top: 2px solid currentColor; border-right: 2px solid currentColor; transform: rotate(45deg) translateY(-1px); transition: transform .2s var(--ease); }
211
+ table.loops tbody tr:hover td.go i { transform: translateX(3px) rotate(45deg) translateY(-1px); }
212
+ table.loops tbody tr:hover { background: var(--tint); }
213
+ table.loops td.name a { color: var(--text); font: 600 var(--t16)/1.3 var(--font-display); letter-spacing: -.01em; }
214
+ table.loops td.name span { display: block; color: var(--text-2); font-size: 13px; margin-top: 2px; }
215
+ table.loops td.goal { color: var(--text-2); max-width: 46ch; }
216
+ /* A project is a repository; its loops are the rows under it. */
217
+ table.loops tr.project th { padding-top: var(--s2); padding-bottom: 10px; text-align: left; background: var(--tint); font-weight: 400; }
218
+ table.loops tr.project:hover { background: none; }
219
+ table.loops tr.project .eyebrow { margin-right: var(--s1); }
220
+ table.loops tr.project b { font: 700 var(--t16)/1.3 var(--font-display); letter-spacing: -.01em; color: var(--text); }
221
+ table.loops tr.project .where { margin-left: 12px; color: var(--text-2); font-size: 13px; overflow-wrap: anywhere; }
222
+ table.loops tr.project .waits { position: relative; margin-left: 12px; padding: 2px 10px; border-radius: var(--pill); background: var(--accent); color: var(--on-accent); font-size: var(--t12); font-weight: 600; white-space: nowrap; }
223
+ table.loops tr.project .waits::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 6px; border-radius: 50%; background: currentColor; vertical-align: 1px; }
224
+ table.loops tbody tr:not(.project) td.name { padding-left: var(--s4); }
225
+ table.loops td.name span.id { font-family: var(--mono); font-size: var(--t12); }
226
+ table.loops .asks { margin-left: 6px; color: var(--accent); font-size: var(--t12); font-weight: 600; white-space: nowrap; }
227
+ table.loops .asks::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 4px; border-radius: 50%; background: currentColor; vertical-align: 1px; }
228
+
229
+ /* ---- the picture of a round ---- */
230
+ .scroll { overflow-x: auto; }
231
+ svg.round { display: block; width: 100%; min-width: 760px; height: auto; }
232
+ svg.round text { font-family: var(--font); fill: var(--text); }
233
+ svg.round .edge, svg.round .back { fill: none; stroke: var(--idle-mark); stroke-width: 1.3; }
234
+ svg.round .back { stroke-dasharray: 4 5; }
235
+ svg.round .tip { fill: var(--idle-mark); }
236
+ svg.round .node .box { fill: var(--surface); stroke: var(--line); stroke-width: 1.2; }
237
+ svg.round .node .bar { fill: var(--idle-mark); }
238
+ svg.round .node .kind { font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; fill: var(--text-2); }
239
+ svg.round .node .name { font-size: 13.5px; font-weight: 600; }
240
+ svg.round .node.ok .bar { fill: var(--ok-mark); }
241
+ svg.round .node.failed .box { stroke: var(--bad-mark); } svg.round .node.failed .bar { fill: var(--bad-mark); }
242
+ svg.round .node.running .box { stroke: var(--run-mark); stroke-width: 1.6; } svg.round .node.running .bar { fill: var(--run-mark); animation: beat 1.6s var(--ease) infinite; }
243
+ svg.round .node.skipped .box { stroke-dasharray: 4 3; }
244
+ svg.round .node.pending .name, svg.round .node.skipped .name { fill: var(--text-2); }
245
+ svg.round .badge circle { fill: var(--text); stroke: var(--surface); stroke-width: 2; } svg.round .badge text { fill: var(--bg); font-size: 11px; font-weight: 700; text-anchor: middle; }
246
+ svg.round .count { font-size: 11px; fill: var(--text-2); text-anchor: end; }
247
+ svg.round .backlabel { font-size: 11.5px; fill: var(--text-2); text-anchor: middle; }
248
+ .legend { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3); margin-top: var(--s2); padding-top: var(--s2); border-top: 1px solid var(--line); font-size: 13px; color: var(--text-2); }
249
+ .legend span { display: inline-flex; align-items: center; gap: var(--s1); }
250
+
251
+ /* A step's state in a cell or the legend: finished a filled dot, running a ring that beats,
252
+ failed a bar, skipped a dashed ring, not run a faint dot. */
253
+ .mark { display: inline-block; flex: none; width: 10px; height: 10px; border-radius: 50%; background: var(--tint-2); }
254
+ .mark.ok { background: var(--ok-mark); }
255
+ .mark.running { background: transparent; box-shadow: inset 0 0 0 2.5px var(--run-mark); animation: beat 1.6s var(--ease) infinite; }
256
+ .mark.failed { background: var(--bad-mark); border-radius: 1px; height: 3px; width: 11px; }
257
+ .mark.skipped { background: transparent; box-shadow: inset 0 0 0 1.5px var(--idle-mark); opacity: .7; }
258
+ .mark.find { width: auto; height: auto; min-width: 20px; padding: 1px 6px; border-radius: 6px; background: var(--heat-3); color: var(--on-heat); font-size: 11px; font-weight: 700; text-align: center; }
259
+
260
+ /* ---- rounds against steps ---- */
261
+ table.grid { width: auto; }
262
+ table.grid th, table.grid td { padding: 5px 4px; text-align: center; border-bottom: 0; }
263
+ table.grid th.step, table.grid td.step { text-align: left; padding: 5px var(--s3) 5px var(--s3); white-space: nowrap; position: sticky; left: 0; background: var(--surface); }
264
+ table.grid thead th { padding-top: var(--s2); padding-bottom: var(--s1); }
265
+ table.grid td.step b { font-weight: 600; } table.grid td.step span { color: var(--text-2); font-size: var(--t12); margin-left: var(--s1); }
266
+ table.grid th.rnd a { display: inline-grid; place-items: center; min-width: 34px; height: 34px; padding: 0 var(--s1); border-radius: var(--pill); color: var(--text); font-size: 13px; font-weight: 600; transition: background .2s var(--ease); }
267
+ table.grid th.rnd a:hover { background: var(--tint-2); text-decoration: none; }
268
+ table.grid th.rnd a[aria-current="true"] { background: var(--text); color: var(--bg); }
269
+ table.grid td.on { background: var(--tint); }
270
+ table.grid tbody tr:first-child td.on { border-radius: 10px 10px 0 0; } table.grid tbody tr:last-child td.on { border-radius: 0 0 10px 10px; }
271
+ .cell { display: inline-grid; place-items: center; width: 34px; height: 28px; border-radius: 7px; font-size: var(--t12); font-weight: 700; font-variant-numeric: tabular-nums; }
272
+ .cell.h1 { background: var(--heat-1); } .cell.h2 { background: var(--heat-2); }
273
+ .cell.h3 { background: var(--heat-3); color: var(--on-heat); } .cell.h4 { background: var(--heat-4); color: var(--on-heat); }
274
+ table.grid tr.sum td { font-size: var(--t12); color: var(--text-2); font-variant-numeric: tabular-nums; white-space: nowrap; padding-top: var(--s1); }
275
+ table.grid tr.sum.first td { border-top: 1px solid var(--line); padding-top: 12px; }
276
+ table.grid tr.sum:last-child td { padding-bottom: var(--s2); }
277
+ table.grid tr.sum td.step { font-weight: 600; }
278
+ .v { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; }
279
+ .v.done { color: var(--ok); } .v.failed { color: var(--bad); } .v.stopped { color: var(--warn); } .v.continue { color: var(--text-2); } .v.live { color: var(--run); } .v.wait { color: var(--accent); }
280
+
281
+ /* Rounds: pick a round on the left, read it on the right; many rounds stack instead. */
282
+ .split { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s3); align-items: start; }
283
+ .split .pick { position: sticky; top: 84px; }
284
+ .split.stacked { grid-template-columns: minmax(0, 1fr); } .split.stacked .pick { position: static; }
285
+ .split .detail .round-head { margin-top: 0; }
286
+ @media (max-width: 1100px) { .split { grid-template-columns: minmax(0, 1fr); } .split .pick { position: static; } }
287
+
288
+ /* ---- one round ---- */
289
+ .round-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px var(--s2); margin: var(--s5) 0 var(--s2); scroll-margin-top: 80px; }
290
+ .round-head h2 { margin: 0; font-size: var(--t26); letter-spacing: -.03em; }
291
+ .plan { padding: 2px 0 2px var(--s2); border-left: 3px solid var(--accent); max-width: 90ch; margin-bottom: var(--s2); }
292
+ .plan p:not(.eyebrow) { margin-top: 4px; white-space: pre-line; }
293
+ table.runs td.what { white-space: nowrap; } table.runs td.what b { font-weight: 600; } table.runs td.what span { display: block; color: var(--text-2); font-size: var(--t12); }
294
+ table.runs td.sum { max-width: 68ch; white-space: pre-line; }
295
+ table.runs ol { margin: var(--s1) 0 0; padding: 0; list-style: none; white-space: normal; }
296
+ table.runs ol li { position: relative; padding: 3px 0 3px 30px; line-height: 1.45; }
297
+ table.runs ol li::before { content: attr(data-id); position: absolute; left: 0; top: 4px; min-width: 20px; height: 20px; padding: 0 5px; border-radius: 6px; display: inline-grid; place-items: center; font-size: 11px; font-weight: 700; background: var(--heat-4); color: var(--on-heat); }
298
+ table.runs ol li.minor::before { background: transparent; color: var(--text-2); box-shadow: inset 0 0 0 1.5px var(--idle-mark); }
299
+ table.runs ol li.closed { color: var(--text-2); } table.runs ol li.closed::before { background: var(--ok-bg); color: var(--ok); }
300
+ table.runs ol li em { font-style: normal; color: var(--ok); }
301
+ .st { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; white-space: nowrap; }
302
+ .st.ok { color: var(--ok); } .st.failed { color: var(--bad); } .st.running { color: var(--run); } .st.skipped { color: var(--text-2); }
303
+ .outcome { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); gap: 12px; margin-top: 12px; }
304
+ .outcome .card p:not(.eyebrow) { margin-top: 6px; white-space: pre-line; }
305
+
306
+ /* ---- items against kinds of check ---- */
307
+ table.items td, table.items th { text-align: center; } table.items td.item, table.items th.item { text-align: left; font-weight: 600; }
308
+ .res { display: inline-flex; align-items: center; min-height: 24px; padding: 0 10px; border-radius: var(--pill); font-size: var(--t12); font-weight: 700; }
309
+ .res.pass { background: var(--ok-bg); color: var(--ok); } .res.partly { background: var(--warn-bg); color: var(--warn); }
310
+ .res.fail { background: var(--bad-bg); color: var(--bad); } .res.blocked { background: var(--idle-bg); color: var(--idle); }
311
+ table.items td.void { background: repeating-linear-gradient(135deg, transparent 0 6px, var(--tint-2) 6px 7px); }
312
+ table.items summary { list-style: none; cursor: pointer; border-radius: var(--radius); padding: 4px; } table.items summary::-webkit-details-marker { display: none; }
313
+ table.items .when { display: block; font-size: var(--t12); color: var(--text-2); margin-top: 3px; }
314
+ table.items details ul { margin: var(--s1) auto 0; padding: 0; list-style: none; text-align: left; font-size: var(--t12); color: var(--text-2); max-width: 36ch; }
315
+
316
+ /* ---- a file of the loop, and the standard ---- */
317
+ .prose { max-width: 84ch; line-height: 1.65; }
318
+ .prose h1 { font-size: var(--t26); margin: 0 0 var(--s2); } .prose h2 { margin-top: var(--s4); } .prose h3 { margin-top: var(--s3); }
319
+ .prose p, .prose ul, .prose ol { margin: 12px 0; }
320
+ .prose pre { background: var(--tint); padding: var(--s2); border-radius: var(--radius); overflow-x: auto; line-height: 1.5; }
321
+ .prose pre code { background: none; padding: 0; }
322
+ .prose table { margin: var(--s2) 0; } .prose th, .prose td { padding: 8px 12px; }
323
+ .prose blockquote { margin: var(--s2) 0; padding-left: var(--s2); border-left: 3px solid var(--line); color: var(--text-2); }
324
+ pre.raw { background: var(--surface); box-shadow: var(--shadow); padding: var(--s3); border-radius: var(--radius-l); overflow-x: auto; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
325
+ .how { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr)); gap: 12px; margin: var(--s4) 0 0; }
326
+ .how .card { display: flex; flex-direction: column; gap: 10px; }
327
+ .how h3 { display: flex; align-items: center; gap: 10px; }
328
+ .how b.n { display: inline-grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--accent-tint); color: var(--accent); font: 700 13px/1 var(--font-display); }
329
+ .how p { color: var(--text-2); font-size: var(--t14); }
330
+ .how pre { margin: 0; padding: 12px 14px; background: var(--tint); border-radius: var(--radius); overflow-x: auto; font-size: 12px; white-space: pre-wrap; word-break: break-all; }
331
+ .how .act { margin-top: auto; display: flex; align-items: center; gap: 12px; }
332
+
333
+ @media (max-width: 900px) {
334
+ .steer { grid-template-columns: 1fr; }
335
+ main, .foot { padding-left: var(--s2); padding-right: var(--s2); }
336
+ .top { padding: var(--s1) var(--s2); }
337
+ .top .where { display: none; }
338
+ h1 { font-size: var(--t26); }
339
+ form.say { grid-template-columns: 1fr; }
340
+ }
341
+ @media (prefers-reduced-motion: reduce) {
342
+ *, *::before, *::after { animation: none !important; transition: none !important; }
343
+ }
344
+
345
+ /* ---- answers offered as buttons, and the inbox ---- */
346
+ form.choices { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 var(--s2); }
347
+ form.choices button { background: var(--surface); color: var(--text); border: 1px solid var(--accent); }
348
+ form.choices button:hover { background: var(--accent); color: var(--on-accent); }
349
+ table.inbox tr.forged { background: var(--bad-bg); }
350
+ .bad { color: var(--bad); }
351
+
352
+ /* ---- caps as bars, and the trend of each measure ---- */
353
+ .caps { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); gap: var(--s2) var(--s3); margin-top: var(--s3); }
354
+ .cap p { display: flex; justify-content: space-between; align-items: baseline; margin: 0 0 6px; font-size: var(--t14); }
355
+ .cap svg { display: block; width: 100%; height: 6px; }
356
+ .cap .track { fill: var(--tint-2); }
357
+ .cap .fill { fill: var(--run-mark); }
358
+ .cap.warn .fill { fill: var(--warn-mark); }
359
+ .cap.warn .num { color: var(--warn); font-weight: 600; }
360
+ .trends { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); gap: var(--s2); }
361
+ figure.trend { margin: 0; }
362
+ figure.trend figcaption { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin-bottom: 8px; }
363
+ figure.trend figcaption b { font: 600 var(--t20)/1.2 var(--font-display); font-variant-numeric: tabular-nums; }
364
+ figure.trend figcaption .grey { font-size: var(--t12); }
365
+ figure.trend svg { display: block; width: 100%; height: auto; overflow: visible; }
366
+ figure.trend .line { fill: none; stroke: var(--text-2); stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; }
367
+ figure.trend circle { fill: var(--surface); stroke: var(--text-2); stroke-width: 1.5; }
368
+ figure.trend circle:last-of-type { fill: var(--text-2); }
369
+ figure.trend.better .line, figure.trend.better circle { stroke: var(--ok-mark); }
370
+ figure.trend.better circle:last-of-type { fill: var(--ok-mark); }
371
+ figure.trend.worse .line, figure.trend.worse circle { stroke: var(--warn-mark); }
372
+ figure.trend.worse circle:last-of-type { fill: var(--warn-mark); }
373
+ figure.trend .target { stroke: var(--text-3, var(--text-2)); stroke-width: 1; stroke-dasharray: 3 3; opacity: .6; }
374
+ .v.discarded { color: var(--warn); }
375
+ p.metrics { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 0 0 var(--s2); font-size: var(--t14); }
376
+ p.metrics b { font-variant-numeric: tabular-nums; }
377
+ p.metrics .met b { color: var(--ok); }
378
+ p.metrics .short b { color: var(--warn); }
379
+ p.err { margin: 0 0 4px; color: var(--bad); font-weight: 600; }
380
+ p.err.person { color: var(--accent); }
381
+ section.stalled { margin-top: var(--s3); border-color: var(--warn-mark); }
382
+ section.stalled .eyebrow { color: var(--warn); }
383
+ ol.steps { margin: var(--s1) 0 0; padding-left: 1.3em; }
384
+ ol.steps li { margin: 4px 0; }
385
+
386
+ /* ---- filters on the first page, and the first run ---- */
387
+ form.filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: var(--s3) 0 0; }
388
+ form.filters input[type="search"], form.filters select { min-height: 38px; padding: 6px 12px; font: var(--t14)/1.3 var(--font); color: var(--text); background: var(--surface); border: 1px solid var(--line); border-radius: 10px; }
389
+ form.filters input[type="search"] { min-width: min(320px, 100%); }
390
+ form.filters .tick { font-size: var(--t14); display: flex; gap: 6px; align-items: center; }
391
+ .start-here .lead { font-size: var(--t16); max-width: 70ch; }
392
+ .start-here .prompts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: var(--s2); }
393
+ pre.prompt { margin: var(--s1) 0 var(--s2); white-space: normal; overflow-wrap: anywhere; font: var(--t14)/1.55 var(--mono); }
394
+ /* a loop's latest rounds on the overview: darker for more findings left open, a dot for none, a ring while it runs */
395
+ table.loops td.name { min-width: 230px; }
396
+ /* where to look first: a stripe in the status colour at the row's left edge, a tint where a person is needed */
397
+ table.loops tr.is-waiting td.name { box-shadow: inset 4px 0 0 var(--accent); } table.loops tr.is-waiting { background: var(--accent-tint); }
398
+ table.loops tr.is-failed td.name { box-shadow: inset 4px 0 0 var(--bad-mark); } table.loops tr.is-failed { background: var(--bad-bg); }
399
+ table.loops tr.is-stalled td.name, table.loops tr.is-capped td.name { box-shadow: inset 4px 0 0 var(--warn-mark); }
400
+ table.loops tr.is-running td.name, table.loops tr.is-sleeping td.name { box-shadow: inset 4px 0 0 var(--run-mark); }
401
+ table.loops tr.is-done td.name { box-shadow: inset 4px 0 0 var(--ok-mark); }
402
+ table.loops td.goal span { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
403
+ .trail { display: flex; justify-content: flex-end; align-items: center; gap: 3px; margin-top: 7px; }
404
+ .trail i { width: 10px; height: 14px; border-radius: 3px; background: var(--heat-1); }
405
+ .trail i.h2 { background: var(--heat-2); } .trail i.h3 { background: var(--heat-3); } .trail i.h4 { background: var(--heat-4); }
406
+ .trail i.clear { width: 8px; height: 8px; margin: 0 1px; border-radius: 50%; background: var(--ok-mark); }
407
+ .trail i.live { width: 10px; height: 10px; border-radius: 50%; background: none; box-shadow: inset 0 0 0 2.5px var(--run-mark); }
408
+ table.loops .stuck { display: block; margin-top: 4px; color: var(--warn); font-size: var(--t12); font-weight: 600; white-space: nowrap; }
409
+ nav.agents { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 var(--s1); font-size: var(--t12); }
410
+ nav.agents a { padding: 2px 10px; border-radius: var(--pill); background: var(--tint-2); color: var(--text); text-decoration: none; }
411
+ nav.agents a[aria-current="true"] { background: var(--accent); color: var(--on-accent); }
412
+
413
+ /* ---- a phone: the loops table scrolls in its card, and keeps only what decides a click ---- */
414
+ .card.flush.tabbed { overflow-x: auto; }
415
+ @media (max-width: 640px) {
416
+ table.loops td.goal, table.loops th:nth-child(3),
417
+ table.loops td:nth-child(6), table.loops th:nth-child(6),
418
+ table.loops td:nth-child(8), table.loops th:nth-child(8) { display: none; }
419
+ table.loops tr.project .where { display: block; margin: 4px 0 0; }
420
+ form.filters input[type="search"] { min-width: 0; width: 100%; }
421
+ }
422
+ table.loops tr.project th { white-space: normal; }
423
+ .prose code { overflow-wrap: anywhere; }
424
+ .prose table { display: block; max-width: 100%; overflow-x: auto; }
425
+ main code, .prose a { overflow-wrap: anywhere; }