bacon-tracker 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ .detail-backdrop {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 100;
5
+ display: none;
6
+ align-items: center;
7
+ justify-content: center;
8
+ background: rgba(40, 20, 32, 0.5);
9
+ padding: 24px;
10
+ }
11
+ .detail-backdrop.open {
12
+ display: flex;
13
+ }
14
+ .detail-modal {
15
+ background: #fff;
16
+ border: 1px solid rgba(195, 195, 195, 0.4);
17
+ border-left: 3px solid var(--accent);
18
+ border-radius: var(--radius-sm);
19
+ box-shadow: 0 14px 44px rgba(0, 0, 0, 0.28);
20
+ width: min(680px, 100%);
21
+ max-height: 85vh;
22
+ display: flex;
23
+ flex-direction: column;
24
+ padding: 15px 18px;
25
+ }
26
+ [data-theme="dark"] .detail-modal {
27
+ background: #2a1d26;
28
+ border-color: rgba(255, 255, 255, 0.09);
29
+ }
30
+ .detail-head {
31
+ display: flex;
32
+ align-items: center;
33
+ flex-wrap: wrap;
34
+ gap: 6px;
35
+ margin-bottom: 9px;
36
+ }
37
+ .detail-id {
38
+ font-size: 0.66rem;
39
+ color: var(--fg-muted);
40
+ }
41
+ /* Where the thing stands right now - the story's stage, the record's status.
42
+ The detail view is read on its own, away from the column that would have
43
+ said it, so the pill carries the same colour the column header does. */
44
+ .detail-stage {
45
+ font-size: 0.58rem;
46
+ text-transform: uppercase;
47
+ letter-spacing: 0.06em;
48
+ color: var(--fg-muted);
49
+ background: rgba(195, 195, 195, 0.14);
50
+ border: 1px solid var(--border);
51
+ border-radius: 999px;
52
+ padding: 1px 7px;
53
+ line-height: 1.55;
54
+ white-space: nowrap;
55
+ }
56
+ .detail-stage[data-stage="1_icebox"] {
57
+ color: var(--c-chore);
58
+ background: rgba(107, 144, 160, 0.13);
59
+ border-color: rgba(107, 144, 160, 0.4);
60
+ }
61
+ .detail-stage[data-stage="2_backlog"] {
62
+ color: var(--accent);
63
+ background: rgba(192, 130, 162, 0.14);
64
+ border-color: rgba(192, 130, 162, 0.42);
65
+ }
66
+ .detail-stage[data-stage="3_started"] {
67
+ color: #5e8c6a;
68
+ background: rgba(80, 140, 100, 0.13);
69
+ border-color: rgba(80, 140, 100, 0.4);
70
+ }
71
+ .detail-stage[data-status="proposed"] {
72
+ color: var(--accent);
73
+ background: rgba(192, 130, 162, 0.14);
74
+ border-color: rgba(192, 130, 162, 0.42);
75
+ }
76
+ .detail-stage[data-status="accepted"] {
77
+ color: #5e8c6a;
78
+ background: rgba(80, 140, 100, 0.13);
79
+ border-color: rgba(80, 140, 100, 0.4);
80
+ }
81
+ .detail-stage[data-status="rejected"] {
82
+ color: var(--c-bug);
83
+ background: rgba(212, 112, 112, 0.12);
84
+ border-color: rgba(212, 112, 112, 0.4);
85
+ }
86
+ .detail-close {
87
+ margin-left: auto;
88
+ background: none;
89
+ border: none;
90
+ color: var(--fg-muted);
91
+ cursor: pointer;
92
+ font-size: 1.05rem;
93
+ line-height: 1;
94
+ padding: 0 2px;
95
+ }
96
+ .detail-close:hover {
97
+ color: var(--fg-strong);
98
+ }
99
+ .detail-title {
100
+ font-size: 1.02rem;
101
+ color: var(--fg-strong);
102
+ margin-bottom: 11px;
103
+ word-break: break-word;
104
+ }
105
+ .detail-body {
106
+ overflow-y: auto;
107
+ overflow-x: hidden;
108
+ font-size: 0.82rem;
109
+ }
@@ -0,0 +1,82 @@
1
+ /* The one header (BT-172): dark bar, carrot, theme toggle - shared by the
2
+ board, the dashboard, the docs browser and the decisions board. */
3
+ header {
4
+ background: var(--fg-strong);
5
+ color: var(--bg);
6
+ padding: 9px 14px;
7
+ display: flex;
8
+ align-items: center;
9
+ gap: 10px;
10
+ flex-shrink: 0;
11
+ }
12
+ header h1 {
13
+ font-size: 0.85rem;
14
+ font-weight: 400;
15
+ letter-spacing: -0.5px;
16
+ color: var(--color-pink);
17
+ }
18
+ .ns {
19
+ font-size: 0.63rem;
20
+ letter-spacing: 0.1em;
21
+ color: var(--accent);
22
+ border: 1px solid var(--accent);
23
+ padding: 1px 7px;
24
+ border-radius: var(--radius-sm);
25
+ }
26
+ .back-link {
27
+ color: rgba(255, 227, 240, 0.45);
28
+ font-size: 0.72rem;
29
+ text-decoration: none;
30
+ padding: 2px 8px;
31
+ border-radius: var(--radius-sm);
32
+ border: 1px solid rgba(255, 227, 240, 0.18);
33
+ transition:
34
+ color 0.12s,
35
+ border-color 0.12s;
36
+ }
37
+ .back-link:hover {
38
+ color: var(--color-pink);
39
+ border-color: rgba(255, 227, 240, 0.45);
40
+ }
41
+ .header-spacer {
42
+ flex: 1;
43
+ }
44
+ .accent-dot {
45
+ width: 6px;
46
+ height: 6px;
47
+ border-radius: 50%;
48
+ background: var(--accent);
49
+ }
50
+ .carrot-mark {
51
+ font-size: 12px;
52
+ flex-shrink: 0;
53
+ opacity: 0.45;
54
+ line-height: 1;
55
+ }
56
+ .header-meta {
57
+ color: rgba(255, 227, 240, 0.45);
58
+ font-size: 0.72rem;
59
+ }
60
+ .header-meta a {
61
+ color: rgba(255, 227, 240, 0.65);
62
+ }
63
+ .theme-toggle {
64
+ background: none;
65
+ border: 1px solid rgba(255, 227, 240, 0.18);
66
+ color: rgba(255, 227, 240, 0.38);
67
+ font-family: var(--font-mono);
68
+ font-size: 0.72rem;
69
+ padding: 2px 7px;
70
+ border-radius: var(--radius-sm);
71
+ cursor: pointer;
72
+ transition:
73
+ color 0.12s,
74
+ border-color 0.12s;
75
+ }
76
+ .theme-toggle:hover {
77
+ color: var(--color-pink);
78
+ border-color: rgba(255, 227, 240, 0.5);
79
+ }
80
+ [data-theme="dark"] header {
81
+ background: #130f12;
82
+ }
@@ -0,0 +1,81 @@
1
+ /* Rendered-markdown styling (BT-172), shared by the docs preview and every
2
+ detail overlay - content must read the same wherever a page appears. The
3
+ global reset zeroes list padding, so ol/ul restore it here or their markers
4
+ clip, which is what read as "the parser is broken". */
5
+ .md-content {
6
+ font-size: 0.88rem;
7
+ line-height: 1.55;
8
+ }
9
+ .md-content h1,
10
+ .md-content h2,
11
+ .md-content h3 {
12
+ color: var(--fg-strong);
13
+ font-weight: 400;
14
+ margin: 1.1em 0 0.45em;
15
+ }
16
+ .md-content h1 {
17
+ font-size: 1.25rem;
18
+ }
19
+ .md-content h2 {
20
+ font-size: 1.05rem;
21
+ }
22
+ .md-content p {
23
+ margin: 0.55em 0;
24
+ }
25
+ .md-content ul,
26
+ .md-content ol {
27
+ margin: 0.55em 0;
28
+ padding-left: 1.5em;
29
+ }
30
+ .md-content li {
31
+ margin: 0.15em 0;
32
+ }
33
+ .md-content a {
34
+ color: var(--accent);
35
+ }
36
+ .md-content code {
37
+ background: rgba(127, 127, 127, 0.14);
38
+ padding: 0.1em 0.3em;
39
+ border-radius: 3px;
40
+ font-size: 0.95em;
41
+ }
42
+ .md-content pre {
43
+ background: rgba(127, 127, 127, 0.1);
44
+ padding: 0.7rem 0.9rem;
45
+ border-radius: var(--radius-sm);
46
+ overflow-x: auto;
47
+ margin: 0.55em 0;
48
+ }
49
+ .md-content pre code {
50
+ background: none;
51
+ padding: 0;
52
+ }
53
+ .md-content table {
54
+ border-collapse: collapse;
55
+ margin: 0.7em 0;
56
+ display: block;
57
+ overflow-x: auto;
58
+ }
59
+ .md-content th,
60
+ .md-content td {
61
+ border: 1px solid var(--border);
62
+ padding: 0.3em 0.6em;
63
+ text-align: left;
64
+ }
65
+ .md-content th {
66
+ color: var(--fg-strong);
67
+ }
68
+ .md-content blockquote {
69
+ border-left: 3px solid var(--accent);
70
+ margin: 0.7em 0;
71
+ padding: 0.1em 0 0.1em 0.9em;
72
+ color: var(--fg-muted);
73
+ }
74
+ .md-content input[type="checkbox"] {
75
+ accent-color: var(--accent);
76
+ }
77
+ .md-content hr {
78
+ border: none;
79
+ border-top: 1px solid var(--border);
80
+ margin: 1em 0;
81
+ }
@@ -0,0 +1,41 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ :root {
10
+ --color-pink: #ffe3f0;
11
+ --color-dusty-rose: #c082a2;
12
+ --color-warm-grey: #5d5d5d;
13
+ --color-grey-light: #c3c3c3;
14
+ --color-grey-dark: #1d1d1d;
15
+
16
+ --bg: var(--color-pink);
17
+ --fg: var(--color-warm-grey);
18
+ --fg-strong: var(--color-grey-dark);
19
+ --fg-muted: #7a7080;
20
+ --accent: var(--color-dusty-rose);
21
+ --border: rgba(195, 195, 195, 0.55);
22
+
23
+ --font-mono: "Source Code Pro", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
24
+
25
+ --c-feature: #c082a2;
26
+ --c-bug: #d47070;
27
+ --c-chore: #6b90a0;
28
+
29
+ --radius-card: 12px;
30
+ --radius-sm: 6px;
31
+ --radius-input: 5px;
32
+ }
33
+
34
+ /* Shared dark palette - page-specific dark rules stay in each template. */
35
+ [data-theme="dark"] {
36
+ --bg: #1c1218;
37
+ --fg: #bfb0bc;
38
+ --fg-strong: #e8dde5;
39
+ --fg-muted: #7a6b77;
40
+ --border: rgba(255, 255, 255, 0.1);
41
+ }
@@ -0,0 +1 @@
1
+ (function(){var t=localStorage.getItem('bt-theme')||(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');document.documentElement.dataset.theme=t;}());
@@ -0,0 +1,281 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bacon Dashboard</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,300;0,400;1,400&display=swap" rel="stylesheet">
10
+ <script><%= erb :'_theme_boot', layout: false %></script>
11
+ <style>
12
+ <%= erb :'_root_css', layout: false %>
13
+ .project-card {
14
+ cursor: pointer;
15
+ }
16
+ .pcard-actions {
17
+ display: flex;
18
+ gap: 0.75rem;
19
+ margin-top: 0.6rem;
20
+ font-size: 0.78rem;
21
+ }
22
+ .pcard-actions a {
23
+ color: var(--fg-muted);
24
+ text-decoration: none;
25
+ border: 1px solid var(--border);
26
+ border-radius: var(--radius-sm);
27
+ padding: 0.15rem 0.55rem;
28
+ }
29
+ .pcard-actions a:hover {
30
+ color: var(--accent);
31
+ border-color: var(--accent);
32
+ }
33
+ .pcard-docsline span + span::before {
34
+ content: "· ";
35
+ color: var(--fg-muted);
36
+ }
37
+
38
+ body {
39
+ font-family: var(--font-mono);
40
+ font-size: 14px;
41
+ background: var(--bg);
42
+ color: var(--fg);
43
+ min-height: 100dvh;
44
+ }
45
+
46
+ <%= erb :'_header_css', layout: false %>
47
+
48
+ .grid {
49
+ display: grid;
50
+ grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
51
+ gap: 14px;
52
+ padding: 18px 16px;
53
+ max-width: 1100px;
54
+ }
55
+
56
+ @media (max-width: 600px) {
57
+ .grid {
58
+ grid-template-columns: 1fr;
59
+ padding: 12px;
60
+ gap: 10px;
61
+ }
62
+ }
63
+
64
+ .project-card {
65
+ background: #fff;
66
+ border-radius: 10px;
67
+ padding: 14px 16px;
68
+ border: 1px solid rgba(192, 130, 162, 0.2);
69
+ border-left: 3px solid var(--accent);
70
+ text-decoration: none;
71
+ color: inherit;
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: 9px;
75
+ transition:
76
+ box-shadow 0.15s,
77
+ transform 0.1s;
78
+ }
79
+ .project-card:hover {
80
+ box-shadow: 0 4px 18px rgba(192, 130, 162, 0.18);
81
+ transform: translateY(-1px);
82
+ }
83
+ .project-card.h-active {
84
+ border-left-color: #5e8c6a;
85
+ }
86
+ .project-card.h-idle {
87
+ border-left-color: #d47070;
88
+ }
89
+ .project-card.h-overload {
90
+ border-left-color: #b08030;
91
+ }
92
+
93
+ .pcard-top {
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 8px;
97
+ }
98
+ .pcard-name {
99
+ font-size: 0.88rem;
100
+ color: var(--fg-strong);
101
+ flex: 1;
102
+ letter-spacing: -0.5px;
103
+ }
104
+ .pcard-ns {
105
+ font-size: 0.6rem;
106
+ color: var(--accent);
107
+ border: 1px solid var(--accent);
108
+ padding: 1px 6px;
109
+ border-radius: 4px;
110
+ letter-spacing: 0.08em;
111
+ flex-shrink: 0;
112
+ }
113
+
114
+ .pcard-progress {
115
+ height: 3px;
116
+ background: rgba(192, 130, 162, 0.18);
117
+ border-radius: 2px;
118
+ overflow: hidden;
119
+ }
120
+ .pcard-fill {
121
+ height: 100%;
122
+ background: var(--accent);
123
+ border-radius: 2px;
124
+ }
125
+
126
+ .pcard-counts {
127
+ display: flex;
128
+ gap: 10px;
129
+ font-size: 0.68rem;
130
+ flex-wrap: wrap;
131
+ }
132
+ .ct-done {
133
+ color: var(--accent);
134
+ }
135
+ .ct-icebox {
136
+ color: var(--fg-muted);
137
+ }
138
+ .h-ok {
139
+ color: #5e8c6a;
140
+ }
141
+ .h-warn {
142
+ color: #b08030;
143
+ }
144
+ .h-empty {
145
+ color: #d47070;
146
+ }
147
+
148
+ .pcard-next {
149
+ font-size: 0.7rem;
150
+ color: var(--fg);
151
+ background: rgba(192, 130, 162, 0.07);
152
+ border-radius: 4px;
153
+ padding: 5px 8px;
154
+ border-left: 2px solid var(--accent);
155
+ white-space: nowrap;
156
+ overflow: hidden;
157
+ text-overflow: ellipsis;
158
+ }
159
+ .next-label {
160
+ color: var(--accent);
161
+ margin-right: 4px;
162
+ font-size: 0.62rem;
163
+ }
164
+
165
+ .empty {
166
+ grid-column: 1 / -1;
167
+ text-align: center;
168
+ color: var(--fg);
169
+ font-size: 0.85rem;
170
+ padding: 60px 20px;
171
+ opacity: 0.6;
172
+ }
173
+ .empty code {
174
+ font-family: var(--font-mono);
175
+ background: rgba(192, 130, 162, 0.12);
176
+ padding: 1px 5px;
177
+ border-radius: 3px;
178
+ color: var(--accent);
179
+ }
180
+
181
+ /* ── Dark mode (palette lives in _root_css) ─────────────── */
182
+
183
+ [data-theme="dark"] .project-card {
184
+ background: #2a1d26;
185
+ border-color: rgba(255, 255, 255, 0.08);
186
+ }
187
+ </style>
188
+ </head>
189
+ <body>
190
+ <header>
191
+ <div class="accent-dot"></div>
192
+ <span class="carrot-mark" aria-hidden="true">🥕</span>
193
+ <h1>bacon dashboard</h1>
194
+ <div class="header-spacer"></div>
195
+ <button class="theme-toggle" title="dark mode">◐</button>
196
+ </header>
197
+ <main class="grid">
198
+ <% if @project_stats.empty? %>
199
+ <div class="empty">
200
+ No projects found.<br>
201
+ Add projects to your <code>dashboard.md</code>.
202
+ </div>
203
+ <% else %>
204
+ <% @project_stats.each do |proj| %>
205
+ <%
206
+ hc = if proj[:started] > 2 then 'h-overload'
207
+ elsif proj[:started] > 0 then 'h-active'
208
+ elsif proj[:backlog] > 0 then 'h-idle'
209
+ else '' end
210
+ %>
211
+ <%
212
+ board_href = "/projects/#{proj[:slug]}"
213
+ docs_href = "#{board_href}/docs"
214
+ primary = proj[:tracker] ? board_href : docs_href
215
+ %>
216
+ <div class="project-card <%= hc %>" data-href="<%= primary %>" tabindex="0" role="link">
217
+ <div class="pcard-top">
218
+ <span class="pcard-name"><%= h proj[:name] %></span>
219
+ <span class="pcard-ns"><%= h proj[:namespace] %><% if proj[:version] %> · v<%= h proj[:version] %><% end %></span>
220
+ </div>
221
+ <% if proj[:tracker] %>
222
+ <div class="pcard-progress">
223
+ <div class="pcard-fill" style="width: <%= proj[:progress_pct] %>%"></div>
224
+ </div>
225
+ <div class="pcard-counts">
226
+ <% if proj[:done] > 0 %><span class="ct-done"><%= proj[:done] %> done</span><% end %>
227
+ <% if proj[:started] > 0 %><span class="<%= proj[:started] <= 2 ? 'h-ok' : 'h-warn' %>"><%= proj[:started] %> started</span><% end %>
228
+ <% if proj[:backlog] > 0 %><span class="<%= proj[:backlog] <= 5 ? 'h-ok' : 'h-warn' %>"><%= proj[:backlog] %> backlog</span><% end %>
229
+ <% if proj[:icebox] > 0 %><span class="ct-icebox"><%= proj[:icebox] %> icebox</span><% end %>
230
+ </div>
231
+ <% if proj[:next_task] && !proj[:next_task].empty? %>
232
+ <div class="pcard-next"><span class="next-label">next:</span><%= h proj[:next_task] %></div>
233
+ <% end %>
234
+ <% else %>
235
+ <div class="pcard-counts pcard-docsline">
236
+ <span><%= proj[:pages] %> pages</span>
237
+ <span><%= proj[:decisions] %> decisions</span>
238
+ <% if proj[:proposed] > 0 %><span class="h-warn"><%= proj[:proposed] %> proposed</span><% end %>
239
+ </div>
240
+ <% end %>
241
+ <%
242
+ actions = []
243
+ actions << ["open tracker", board_href] if proj[:tracker]
244
+ actions << ["open docs", docs_href] if proj[:docs]
245
+ actions << ["open adrs", "#{docs_href}/decisions"] if proj[:docs] && proj[:decisions].to_i > 0
246
+ %>
247
+ <% if actions.size > 1 %>
248
+ <div class="pcard-actions">
249
+ <% actions.each do |label, href| %><a href="<%= href %>"><%= label %></a><% end %>
250
+ </div>
251
+ <% end %>
252
+ </div>
253
+ <% end %>
254
+ <% end %>
255
+ </main>
256
+ <script>
257
+ // The card is a div (nested anchors are invalid HTML), so whole-card click
258
+ // navigates to the primary surface; clicks on an inner action win (BT-138).
259
+ document.querySelector('.grid').addEventListener('click', (e) => {
260
+ if (e.target.closest('a')) return;
261
+ const card = e.target.closest('.project-card[data-href]');
262
+ if (card) window.location = card.dataset.href;
263
+ });
264
+ // role="link" promises Enter; Space too, as for a button-like card.
265
+ document.querySelector('.grid').addEventListener('keydown', (e) => {
266
+ if (e.key !== 'Enter' && e.key !== ' ') return;
267
+ const card = e.target.closest('.project-card[data-href]');
268
+ if (!card || e.target !== card) return;
269
+ e.preventDefault();
270
+ window.location = card.dataset.href;
271
+ });
272
+ </script>
273
+ <script src="/theme.js"></script>
274
+ <script>
275
+ document.addEventListener('DOMContentLoaded', () => {
276
+ initTheme();
277
+ document.querySelector('.theme-toggle').addEventListener('click', toggleTheme);
278
+ });
279
+ </script>
280
+ </body>
281
+ </html>