Setting the file. One moment.
Template · Daily Recap · bitwarden/ai-plugins · Skills Docs
ContentsBack to the top of the page 46.10
README
(opens in a new tab)
assets/ template.html
HTML · 1,347 lines · 38 KB
9 request except the brand fonts, so markup that slips through unescaped cannot run or
10 exfiltrate data. Changing the inline script means regenerating its hash (see the skill README).
11 -->
12 < meta
13 http-equiv = "Content-Security-Policy"
14 content = "default-src 'none'; script-src 'sha256-bRcyh2qlLAq4yCSKRCR2o8P3xYNzqxBodVL8iLzGYGM='; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src data:; base-uri 'none'; form-action 'none'"
15 />
16 < title >Daily Recap · {{DATE}}</ title >
17 < link rel = "preconnect" href = "https://fonts.googleapis.com" />
18 < link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin />
19 < link
20 rel = "stylesheet"
21 href = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
22 />
23 < style >
24 /* Bitwarden brand palette: https://bitwarden.com/brand/ */
25 :root {
26 /* Bitwarden primary */
27 --bw-blue : #175ddc ;
28 --bw-deep-blue : #0c3276 ;
29 --bw-teal : #2cdde9 ;
30 --bw-light-teal : #a2f4fd ;
31 --bw-green : #7bf1a8 ;
32 --bw-red : #ff6550 ;
33 --bw-yellow : #fdc700 ;
34 --bw-off-white : #f3f6f9 ;
35 --bw-light-grey : #d8e2eb ;
36 --bw-medium-grey : #99a7b5 ;
37
38 /* Dark surface system tuned to Bitwarden palette */
39 --bg : #06122b ;
40 --bg-elev : #0c1f3f ;
41 --bg-card : #102747 ;
42 --bg-card-hover : #143058 ;
43 --border : #1f3d6e ;
44 --border-soft : #16315a ;
45
46 /* Text */
47 --text : var ( --bw-off-white );
48 --text-dim : var ( --bw-light-grey );
49 --text-muted : var ( --bw-medium-grey );
50
51 /* Semantic: alias to brand tokens */
52 --accent : var ( --bw-blue );
53 --accent-soft : #5b8ae6 ;
54 --accent-bg : rgba ( 23 , 93 , 220 , 0.12 );
55 --green : var ( --bw-green );
56 --green-bg : rgba ( 123 , 241 , 168 , 0.12 );
57 --amber : var ( --bw-yellow );
58 --amber-bg : rgba ( 253 , 199 , 0 , 0.12 );
59 --red : var ( --bw-red );
60 --red-bg : rgba ( 255 , 101 , 80 , 0.12 );
61 --blue : var ( --bw-teal );
62 --blue-bg : rgba ( 44 , 221 , 233 , 0.1 );
63 --purple : var ( --bw-light-teal );
64 --code-bg : #050e22 ;
65 }
66 * {
67 box-sizing : border-box ;
68 margin : 0 ;
69 padding : 0 ;
70 }
71 body {
72 background : var ( --bg );
73 color : var ( --text );
74 font-family :
75 "Inter" ,
76 -apple-system ,
77 BlinkMacSystemFont,
78 "Segoe UI" ,
79 system-ui ,
80 sans-serif ;
81 line-height : 1.6 ;
82 min-height : 100 vh ;
83 background-image :
84 radial-gradient (
85 circle at 15 % 10 % ,
86 rgba ( 23 , 93 , 220 , 0.18 ),
87 transparent 45 %
88 ),
89 radial-gradient (
90 circle at 85 % 90 % ,
91 rgba ( 44 , 221 , 233 , 0.08 ),
92 transparent 45 %
93 );
94 }
95 .page {
96 max-width : 1200 px ;
97 margin : 0 auto ;
98 padding : 60 px 32 px 80 px ;
99 }
100
101 header {
102 border-bottom : 1 px solid var ( --border );
103 padding-bottom : 32 px ;
104 margin-bottom : 32 px ;
105 }
106 .brand-row {
107 display : flex ;
108 align-items : center ;
109 gap : 12 px ;
110 margin-bottom : 24 px ;
111 }
112 .brand-shield {
113 width : 32 px ;
114 height : 32 px ;
115 flex-shrink : 0 ;
116 }
117 .brand-name {
118 font-size : 14 px ;
119 font-weight : 700 ;
120 letter-spacing : -0.01 em ;
121 color : var ( --bw-off-white );
122 }
123 .brand-name .dot {
124 color : var ( --bw-blue );
125 margin : 0 6 px ;
126 }
127 .brand-name .sub {
128 color : var ( --text-muted );
129 font-weight : 500 ;
130 }
131 .eyebrow {
132 display : inline-flex ;
133 align-items : center ;
134 gap : 8 px ;
135 color : var ( --accent );
136 font-size : 12 px ;
137 font-weight : 600 ;
138 letter-spacing : 0.15 em ;
139 text-transform : uppercase ;
140 margin-bottom : 16 px ;
141 }
142 .eyebrow::before {
143 content : "" ;
144 width : 24 px ;
145 height : 1 px ;
146 background : var ( --accent );
147 }
148 h1 {
149 font-size : 44 px ;
150 font-weight : 700 ;
151 letter-spacing : -0.02 em ;
152 line-height : 1.1 ;
153 margin-bottom : 12 px ;
154 }
155 h1 .date {
156 color : var ( --accent );
157 font-variant-numeric : tabular-nums ;
158 }
159 .subtitle {
160 color : var ( --text-dim );
161 font-size : 18 px ;
162 max-width : 760 px ;
163 }
164
165 .stats {
166 display : grid ;
167 grid-template-columns : repeat ( 5 , 1 fr );
168 gap : 16 px ;
169 margin : 32 px 0 0 ;
170 }
171 .stat {
172 background : var ( --bg-card );
173 border : 1 px solid var ( --border-soft );
174 border-radius : 16 px ;
175 padding : 16 px 20 px ;
176 transition :
177 transform 0.15 s ,
178 border-color 0.15 s ;
179 }
180 .stat:hover {
181 transform : translateY ( -1 px );
182 border-color : var ( --border );
183 }
184 .stat-value {
185 font-size : 28 px ;
186 font-weight : 700 ;
187 color : var ( --text );
188 font-variant-numeric : tabular-nums ;
189 line-height : 1 ;
190 margin-bottom : 6 px ;
191 }
192 .stat-label {
193 color : var ( --text-dim );
194 font-size : 11 px ;
195 letter-spacing : 0.05 em ;
196 text-transform : uppercase ;
197 }
198
199 /* Today banner */
200 .today-banner {
201 background : linear-gradient (
202 135 deg ,
203 rgba ( 44 , 221 , 233 , 0.1 ),
204 rgba ( 23 , 93 , 220 , 0.06 )
205 );
206 border : 1 px solid var ( --border );
207 border-left : 4 px solid var ( --bw-teal );
208 border-radius : 20 px ;
209 padding : 18 px 24 px ;
210 margin-bottom : 20 px ;
211 display : flex ;
212 align-items : flex-start ;
213 gap : 18 px ;
214 flex-wrap : wrap ;
215 }
216 .today-banner-title {
217 font-size : 11 px ;
218 font-weight : 700 ;
219 letter-spacing : 0.12 em ;
220 text-transform : uppercase ;
221 color : var ( --purple );
222 margin-bottom : 8 px ;
223 display : flex ;
224 align-items : center ;
225 gap : 8 px ;
226 }
227 .today-banner-title::before {
228 content : "⚡" ;
229 font-size : 14 px ;
230 }
231 .today-banner-content {
232 flex : 1 ;
233 min-width : 240 px ;
234 }
235 .today-banner ul {
236 list-style : none ;
237 padding : 0 ;
238 }
239 .today-banner li {
240 color : var ( --text );
241 font-size : 14 px ;
242 padding : 4 px 0 ;
243 position : relative ;
244 padding-left : 22 px ;
245 }
246 .today-banner li ::before {
247 content : "•" ;
248 position : absolute ;
249 left : 6 px ;
250 color : var ( --purple );
251 font-size : 18 px ;
252 line-height : 1 ;
253 }
254 .today-banner li .ts {
255 color : var ( --text-muted );
256 font-family : "SF Mono" , monospace ;
257 font-size : 11 px ;
258 margin-right : 8 px ;
259 font-variant-numeric : tabular-nums ;
260 }
261 .new-tag {
262 display : inline-block ;
263 padding : 1 px 8 px ;
264 border-radius : 999 px ;
265 background : rgba ( 44 , 221 , 233 , 0.15 );
266 color : var ( --bw-teal );
267 font-size : 10 px ;
268 font-weight : 700 ;
269 letter-spacing : 0.08 em ;
270 text-transform : uppercase ;
271 margin-right : 6 px ;
272 vertical-align : 1 px ;
273 border : 1 px solid rgba ( 44 , 221 , 233 , 0.3 );
274 }
275
276 /* Standup card */
277 .standup {
278 background : linear-gradient (
279 135 deg ,
280 rgba ( 123 , 241 , 168 , 0.06 ),
281 rgba ( 23 , 93 , 220 , 0.04 )
282 );
283 border : 1 px solid var ( --border );
284 border-left : 4 px solid var ( --border );
285 border-radius : 20 px ;
286 padding : 22 px 28 px ;
287 margin-bottom : 32 px ;
288 position : relative ;
289 }
290 .standup [ data-rag = "green" ] {
291 border-left-color : var ( --green );
292 }
293 .standup [ data-rag = "amber" ] {
294 border-left-color : var ( --amber );
295 }
296 .standup [ data-rag = "red" ] {
297 border-left-color : var ( --red );
298 }
299 .standup-header {
300 display : flex ;
301 align-items : center ;
302 justify-content : space-between ;
303 margin-bottom : 14 px ;
304 gap : 12 px ;
305 flex-wrap : wrap ;
306 }
307 .standup-title {
308 display : flex ;
309 align-items : center ;
310 gap : 10 px ;
311 font-size : 13 px ;
312 font-weight : 600 ;
313 letter-spacing : 0.12 em ;
314 text-transform : uppercase ;
315 color : var ( --green );
316 }
317 .standup-title::before {
318 content : "☕" ;
319 font-size : 16 px ;
320 }
321 .standup-meta {
322 color : var ( --text-muted );
323 font-size : 12 px ;
324 font-variant-numeric : tabular-nums ;
325 }
326 .standup-copy {
327 background : transparent ;
328 color : var ( --text-dim );
329 border : 1 px solid var ( --border );
330 padding : 6 px 14 px ;
331 border-radius : 999 px ;
332 font-size : 11 px ;
333 cursor : pointer ;
334 font-family : inherit ;
335 transition : all 0.15 s ;
336 text-transform : uppercase ;
337 letter-spacing : 0.05 em ;
338 font-weight : 600 ;
339 }
340 .standup-copy:hover {
341 color : var ( --text );
342 border-color : var ( --text-muted );
343 }
344 .standup-copy.copied {
345 color : var ( --green );
346 border-color : var ( --green );
347 }
348 .standup-copy.failed {
349 color : var ( --red );
350 border-color : var ( --red );
351 }
352 .standup-bluf {
353 display : flex ;
354 align-items : center ;
355 gap : 10 px ;
356 margin-bottom : 16 px ;
357 font-size : 15 px ;
358 line-height : 1.5 ;
359 color : var ( --text );
360 }
361 .standup-rag-dot {
362 flex : none ;
363 width : 12 px ;
364 height : 12 px ;
365 border-radius : 50 % ;
366 background : var ( --text-muted );
367 }
368 .standup [ data-rag = "green" ] .standup-rag-dot {
369 background : var ( --green );
370 }
371 .standup [ data-rag = "amber" ] .standup-rag-dot {
372 background : var ( --amber );
373 }
374 .standup [ data-rag = "red" ] .standup-rag-dot {
375 background : var ( --red );
376 }
377 .standup-grid {
378 display : grid ;
379 grid-template-columns : repeat ( 3 , 1 fr );
380 gap : 20 px ;
381 }
382 @media ( max-width : 720 px ) {
383 .standup-grid {
384 grid-template-columns : 1 fr ;
385 }
386 }
387 .standup-col h3 {
388 font-size : 11 px ;
389 font-weight : 600 ;
390 letter-spacing : 0.08 em ;
391 text-transform : uppercase ;
392 color : var ( --text-muted );
393 margin-bottom : 8 px ;
394 }
395 .standup-col ul {
396 list-style : none ;
397 padding : 0 ;
398 }
399 .standup-col li {
400 position : relative ;
401 padding-left : 18 px ;
402 margin-bottom : 6 px ;
403 color : var ( --text );
404 font-size : 14 px ;
405 line-height : 1.5 ;
406 }
407 .standup-col.yesterday li ::before {
408 content : "✓" ;
409 position : absolute ;
410 left : 0 ;
411 top : 0 ;
412 color : var ( --green );
413 font-weight : 700 ;
414 }
415 .standup-col.next li ::before {
416 content : "→" ;
417 position : absolute ;
418 left : 0 ;
419 top : 0 ;
420 color : var ( --amber );
421 font-weight : 700 ;
422 }
423 .standup-col.blockers li ::before {
424 content : "!" ;
425 position : absolute ;
426 left : 0 ;
427 top : 0 ;
428 color : var ( --red );
429 font-weight : 700 ;
430 }
431 .standup-col li strong {
432 color : var ( --accent-soft );
433 font-weight : 600 ;
434 }
435
436 /* Toolbar */
437 .toolbar {
438 display : flex ;
439 flex-wrap : wrap ;
440 align-items : center ;
441 gap : 12 px ;
442 background : var ( --bg-card );
443 border : 1 px solid var ( --border-soft );
444 border-radius : 16 px ;
445 padding : 12 px ;
446 margin-bottom : 32 px ;
447 position : sticky ;
448 top : 12 px ;
449 z-index : 10 ;
450 backdrop-filter : blur ( 8 px );
451 }
452 .filter-group {
453 display : flex ;
454 gap : 6 px ;
455 flex-wrap : wrap ;
456 }
457 .chip {
458 background : transparent ;
459 border : 1 px solid var ( --border );
460 color : var ( --text-dim );
461 padding : 6 px 12 px ;
462 border-radius : 999 px ;
463 font-size : 12 px ;
464 font-weight : 500 ;
465 cursor : pointer ;
466 transition : all 0.15 s ;
467 font-family : inherit ;
468 }
469 .chip:hover {
470 color : var ( --text );
471 border-color : var ( --text-muted );
472 }
473 .chip.active {
474 background : var ( --accent-bg );
475 color : var ( --accent );
476 border-color : var ( --accent );
477 }
478 .chip .count {
479 margin-left : 6 px ;
480 padding : 1 px 6 px ;
481 border-radius : 999 px ;
482 background : var ( --border-soft );
483 color : var ( --text-muted );
484 font-size : 10 px ;
485 }
486 .chip.active .count {
487 background : rgba ( 23 , 93 , 220 , 0.2 );
488 color : var ( --accent );
489 }
490 .search {
491 flex : 1 ;
492 min-width : 200 px ;
493 background : var ( --bg );
494 border : 1 px solid var ( --border );
495 color : var ( --text );
496 padding : 9 px 16 px ;
497 border-radius : 999 px ;
498 font-size : 13 px ;
499 font-family : inherit ;
500 }
501 .search:focus {
502 outline : none ;
503 border-color : var ( --accent );
504 box-shadow : 0 0 0 3 px rgba ( 23 , 93 , 220 , 0.18 );
505 }
506 .toolbar-divider {
507 width : 1 px ;
508 height : 22 px ;
509 background : var ( --border );
510 }
511 .view-toggle {
512 display : flex ;
513 gap : 0 ;
514 border : 1 px solid var ( --border );
515 border-radius : 999 px ;
516 overflow : hidden ;
517 }
518 .view-toggle button {
519 background : transparent ;
520 color : var ( --text-dim );
521 border : none ;
522 padding : 8 px 14 px ;
523 cursor : pointer ;
524 font-size : 12 px ;
525 font-weight : 500 ;
526 font-family : inherit ;
527 transition : all 0.15 s ;
528 }
529 .view-toggle button :hover {
530 color : var ( --text );
531 background : var ( --bg-card-hover );
532 }
533 .view-toggle button .active {
534 background : var ( --accent-bg );
535 color : var ( --accent );
536 }
537
538 .project {
539 margin-bottom : 40 px ;
540 transition : opacity 0.2 s ;
541 }
542 .project.hidden {
543 display : none ;
544 }
545 .project-header {
546 display : flex ;
547 align-items : center ;
548 gap : 14 px ;
549 margin-bottom : 20 px ;
550 padding-bottom : 12 px ;
551 border-bottom : 1 px solid var ( --border-soft );
552 }
553 .project-icon {
554 width : 40 px ;
555 height : 40 px ;
556 border-radius : 12 px ;
557 background : var ( --accent-bg );
558 border : 1 px solid var ( --border );
559 display : grid ;
560 place-items : center ;
561 font-size : 18 px ;
562 }
563 .project-title {
564 font-size : 22 px ;
565 font-weight : 600 ;
566 letter-spacing : -0.01 em ;
567 }
568 .project-meta {
569 color : var ( --text-muted );
570 font-size : 13 px ;
571 margin-left : auto ;
572 font-variant-numeric : tabular-nums ;
573 }
574
575 .stream {
576 background : var ( --bg-card );
577 border : 1 px solid var ( --border-soft );
578 border-radius : 20 px ;
579 margin-bottom : 14 px ;
580 transition :
581 border-color 0.15 s ,
582 opacity 0.2 s ;
583 overflow : hidden ;
584 }
585 .stream.hidden {
586 display : none ;
587 }
588 .stream:hover {
589 border-color : var ( --border );
590 }
591 .stream-header {
592 display : flex ;
593 align-items : flex-start ;
594 justify-content : space-between ;
595 gap : 16 px ;
596 padding : 18 px 24 px ;
597 cursor : pointer ;
598 user-select : none ;
599 flex-wrap : wrap ;
600 }
601 .stream-title {
602 font-size : 16 px ;
603 font-weight : 600 ;
604 color : var ( --text );
605 line-height : 1.4 ;
606 flex : 1 ;
607 display : flex ;
608 align-items : center ;
609 gap : 10 px ;
610 }
611 .stream-toggle {
612 color : var ( --text-muted );
613 font-size : 12 px ;
614 transition : transform 0.2 s ;
615 display : inline-block ;
616 }
617 .stream.collapsed .stream-toggle {
618 transform : rotate ( -90 deg );
619 }
620 .stream-body {
621 padding : 0 24 px 20 px ;
622 border-top : 1 px solid var ( --border-soft );
623 padding-top : 16 px ;
624 max-height : 1000 px ;
625 overflow : hidden ;
626 transition :
627 max-height 0.25 s ease ,
628 padding 0.25 s ease ,
629 opacity 0.2 s ease ;
630 }
631 .stream.collapsed .stream-body {
632 max-height : 0 ;
633 padding-top : 0 ;
634 padding-bottom : 0 ;
635 opacity : 0 ;
636 border-top-color : transparent ;
637 }
638 .stream ul {
639 list-style : none ;
640 padding : 0 ;
641 }
642 .stream li {
643 position : relative ;
644 padding-left : 20 px ;
645 margin-bottom : 10 px ;
646 color : var ( --text-dim );
647 font-size : 14.5 px ;
648 }
649 .stream li ::before {
650 content : "▸" ;
651 position : absolute ;
652 left : 0 ;
653 top : 0 ;
654 color : var ( --accent );
655 font-size : 12 px ;
656 }
657 .stream li strong {
658 color : var ( --text );
659 font-weight : 600 ;
660 }
661 .stream li em {
662 color : var ( --accent-soft );
663 font-style : normal ;
664 }
665 .stream li .highlight {
666 background : rgba ( 44 , 221 , 233 , 0.08 );
667 border-radius : 4 px ;
668 padding-right : 4 px ;
669 }
670
671 .badge {
672 display : inline-flex ;
673 align-items : center ;
674 gap : 6 px ;
675 padding : 4 px 10 px ;
676 border-radius : 999 px ;
677 font-size : 11 px ;
678 font-weight : 600 ;
679 letter-spacing : 0.04 em ;
680 text-transform : uppercase ;
681 white-space : nowrap ;
682 border : 1 px solid ;
683 }
684 .badge-completed {
685 color : var ( --green );
686 background : var ( --green-bg );
687 border-color : rgba ( 123 , 241 , 168 , 0.25 );
688 }
689 .badge-open {
690 color : var ( --amber );
691 background : var ( --amber-bg );
692 border-color : rgba ( 253 , 199 , 0 , 0.25 );
693 }
694 .badge-deferred {
695 color : var ( --blue );
696 background : var ( --blue-bg );
697 border-color : rgba ( 44 , 221 , 233 , 0.25 );
698 }
699
700 code {
701 font-family : "SF Mono" , "JetBrains Mono" , Menlo, Consolas, monospace ;
702 font-size : 0.88 em ;
703 background : var ( --code-bg );
704 color : var ( --bw-light-teal );
705 padding : 2 px 7 px ;
706 border-radius : 6 px ;
707 border : 1 px solid var ( --border-soft );
708 }
709 .pr-ref {
710 display : inline-flex ;
711 align-items : center ;
712 gap : 4 px ;
713 font-family : "SF Mono" , monospace ;
714 font-size : 0.85 em ;
715 color : var ( --bw-teal );
716 background : var ( --blue-bg );
717 padding : 2 px 10 px ;
718 border-radius : 999 px ;
719 border : 1 px solid rgba ( 44 , 221 , 233 , 0.25 );
720 text-decoration : none ;
721 transition : all 0.15 s ;
722 font-weight : 600 ;
723 }
724 .pr-ref:hover {
725 background : rgba ( 44 , 221 , 233 , 0.18 );
726 border-color : var ( --bw-teal );
727 }
728
729 .timeline {
730 display : none ;
731 }
732 .timeline.active {
733 display : block ;
734 }
735 .projects-view {
736 display : block ;
737 }
738 .projects-view.hidden {
739 display : none ;
740 }
741
742 .tl-header {
743 margin-bottom : 24 px ;
744 color : var ( --text-dim );
745 font-size : 14 px ;
746 }
747 .tl-list {
748 position : relative ;
749 padding-left : 28 px ;
750 border-left : 2 px solid var ( --border-soft );
751 }
752 .tl-event {
753 position : relative ;
754 margin-bottom : 18 px ;
755 background : var ( --bg-card );
756 border : 1 px solid var ( --border-soft );
757 border-radius : 16 px ;
758 padding : 14 px 18 px ;
759 transition : border-color 0.15 s ;
760 }
761 .tl-event:hover {
762 border-color : var ( --border );
763 }
764 .tl-event::before {
765 content : "" ;
766 position : absolute ;
767 left : -36 px ;
768 top : 18 px ;
769 width : 14 px ;
770 height : 14 px ;
771 border-radius : 50 % ;
772 background : var ( --bg );
773 border : 2 px solid var ( --accent );
774 }
775 .tl-event.evt-merged::before {
776 border-color : var ( --purple );
777 }
778 .tl-event.evt-opened::before {
779 border-color : var ( --green );
780 }
781 .tl-event.evt-push::before {
782 border-color : var ( --blue );
783 }
784 .tl-event.evt-review::before {
785 border-color : var ( --amber );
786 }
787 .tl-event.evt-comment::before {
788 border-color : var ( --text-muted );
789 }
790 .tl-event.evt-create::before {
791 border-color : var ( --text-dim );
792 }
793
794 .tl-time {
795 font-family : "SF Mono" , monospace ;
796 font-size : 12 px ;
797 color : var ( --text-muted );
798 font-variant-numeric : tabular-nums ;
799 }
800 .tl-type {
801 display : inline-block ;
802 font-size : 10 px ;
803 font-weight : 700 ;
804 text-transform : uppercase ;
805 letter-spacing : 0.05 em ;
806 padding : 2 px 8 px ;
807 border-radius : 4 px ;
808 margin-left : 8 px ;
809 vertical-align : 1 px ;
810 }
811 .tl-type.t-merged {
812 background : rgba ( 162 , 244 , 253 , 0.12 );
813 color : var ( --bw-light-teal );
814 }
815 .tl-type.t-opened {
816 background : var ( --green-bg );
817 color : var ( --green );
818 }
819 .tl-type.t-push {
820 background : var ( --accent-bg );
821 color : var ( --accent );
822 }
823 .tl-type.t-review {
824 background : var ( --amber-bg );
825 color : var ( --amber );
826 }
827 .tl-type.t-comment {
828 background : rgba ( 153 , 167 , 181 , 0.12 );
829 color : var ( --text-dim );
830 }
831 .tl-type.t-create {
832 background : rgba ( 153 , 167 , 181 , 0.12 );
833 color : var ( --text-dim );
834 }
835 .tl-event-body {
836 margin-top : 6 px ;
837 color : var ( --text );
838 font-size : 14 px ;
839 }
840 .tl-event-body .repo {
841 color : var ( --text-muted );
842 font-family : "SF Mono" , monospace ;
843 font-size : 12 px ;
844 }
845 .tl-quote {
846 margin-top : 8 px ;
847 padding : 8 px 12 px ;
848 border-left : 2 px solid var ( --border );
849 color : var ( --text-dim );
850 font-size : 13 px ;
851 font-style : italic ;
852 }
853 .tl-link-session {
854 margin-top : 6 px ;
855 font-size : 12 px ;
856 color : var ( --accent-soft );
857 display : inline-flex ;
858 align-items : center ;
859 gap : 6 px ;
860 }
861 .tl-link-session::before {
862 content : "⟶" ;
863 color : var ( --accent );
864 }
865
866 .theme-block {
867 background : linear-gradient (
868 135 deg ,
869 rgba ( 23 , 93 , 220 , 0.1 ),
870 rgba ( 44 , 221 , 233 , 0.05 )
871 );
872 border : 1 px solid var ( --border );
873 border-left : 4 px solid var ( --accent );
874 border-radius : 24 px ;
875 padding : 28 px 32 px ;
876 margin-top : 32 px ;
877 }
878 .theme-block h2 {
879 font-size : 13 px ;
880 color : var ( --accent );
881 text-transform : uppercase ;
882 letter-spacing : 0.12 em ;
883 margin-bottom : 14 px ;
884 font-weight : 600 ;
885 }
886 .theme-block p {
887 color : var ( --text );
888 font-size : 15.5 px ;
889 margin-bottom : 14 px ;
890 }
891 .theme-block p :last-child {
892 margin-bottom : 0 ;
893 }
894 .theme-block .open-threads {
895 color : var ( --text-dim );
896 font-size : 14.5 px ;
897 border-top : 1 px solid var ( --border-soft );
898 padding-top : 14 px ;
899 margin-top : 14 px ;
900 }
901 .theme-block .open-threads strong {
902 color : var ( --amber );
903 }
904
905 footer {
906 margin-top : 64 px ;
907 padding-top : 24 px ;
908 border-top : 1 px solid var ( --border );
909 color : var ( --text-muted );
910 font-size : 13 px ;
911 text-align : center ;
912 }
913 footer .invocation {
914 color : var ( --accent );
915 font-weight : 600 ;
916 letter-spacing : 0.04 em ;
917 margin-bottom : 6 px ;
918 }
919 footer a {
920 color : var ( --bw-teal );
921 text-decoration : none ;
922 }
923 footer a :hover {
924 text-decoration : underline ;
925 }
926
927 .hidden {
928 display : none !important ;
929 }
930
931 @keyframes fadeIn {
932 from {
933 opacity : 0 ;
934 transform : translateY ( 4 px );
935 }
936 to {
937 opacity : 1 ;
938 transform : translateY ( 0 );
939 }
940 }
941 .stream ,
942 .tl-event {
943 animation : fadeIn 0.3 s ease both ;
944 }
945
946 @media ( max-width : 720 px ) {
947 .page {
948 padding : 32 px 20 px 60 px ;
949 }
950 h1 {
951 font-size : 32 px ;
952 }
953 .stats {
954 grid-template-columns : repeat ( 2 , 1 fr );
955 }
956 .stream-header {
957 padding : 16 px ;
958 }
959 .stream-body {
960 padding : 0 16 px 18 px ;
961 }
962 .theme-block {
963 padding : 22 px ;
964 }
965 .toolbar {
966 position : relative ;
967 top : 0 ;
968 }
969 }
970 </ style >
971 </ head >
972 < body >
973 < div class = "page" >
974 < header >
975 < div class = "brand-row" >
976 < svg
977 class = "brand-shield"
978 viewBox = "0 0 24 24"
979 fill = "none"
980 xmlns = "http://www.w3.org/2000/svg"
981 aria-hidden = "true"
982 >
983 < path
984 d = "M12 2 L20.5 5 V12.5 C20.5 17.2 16.6 20.6 12 22 C7.4 20.6 3.5 17.2 3.5 12.5 V5 Z"
985 fill = "#175DDC"
986 />
987 < path
988 d = "M12 5 V19.4 C8.6 18.2 6 15.6 6 12.4 V7 Z"
989 fill = "#0C3276"
990 opacity = "0.7"
991 />
992 </ svg >
993 < div class = "brand-name" >
994 Bitwarden< span class = "dot" >·</ span
995 >< span class = "sub" >Engineering Daily Recap</ span >
996 </ div >
997 </ div >
998 < div class = "eyebrow" >Daily Recap · Interactive</ div >
999 < h1 >Engineering Log < span class = "date" >{{DATE}}</ span ></ h1 >
1000 < p class = "subtitle" >{{SUBTITLE}}</ p >
1001
1002 < div class = "stats" >
1003 < div class = "stat" >
1004 < div class = "stat-value" >{{STAT_REPOS}}</ div >
1005 < div class = "stat-label" >Repos Touched</ div >
1006 </ div >
1007 < div class = "stat" >
1008 < div class = "stat-value" >{{STAT_SESSIONS}}</ div >
1009 < div class = "stat-label" >Sessions</ div >
1010 </ div >
1011 < div class = "stat" >
1012 < div class = "stat-value" >{{STAT_PRS_OPENED}}</ div >
1013 < div class = "stat-label" >PRs Opened</ div >
1014 </ div >
1015 < div class = "stat" >
1016 < div class = "stat-value" >{{STAT_PRS_APPROVED}}</ div >
1017 < div class = "stat-label" >PRs Approved</ div >
1018 </ div >
1019 < div class = "stat" >
1020 < div class = "stat-value" >{{STAT_GH_EVENTS}}</ div >
1021 < div class = "stat-label" >GH Events</ div >
1022 </ div >
1023 < div class = "stat" >
1024 < div class = "stat-value" >{{STAT_JIRA_ISSUES}}</ div >
1025 < div class = "stat-label" >Jira issues</ div >
1026 </ div >
1027 < div class = "stat" >
1028 < div class = "stat-value" >{{STAT_CONFLUENCE_PAGES}}</ div >
1029 < div class = "stat-label" >Confluence pages</ div >
1030 </ div >
1031 </ div >
1032 </ header >
1033
1034 {{TODAY_BANNER}}
1035
1036 < div
1037 class = "standup"
1038 id = "standup"
1039 data-date = "{{DATE}}"
1040 data-rag = "{{STANDUP_RAG}}"
1041 >
1042 < div class = "standup-header" >
1043 < div class = "standup-title" >Standup Recap · 30s read</ div >
1044 < div style = "display: flex; align-items: center; gap: 10px" >
1045 < span class = "standup-meta" >{{STANDUP_PERIOD}}</ span >
1046 < button class = "standup-copy" id = "copy-standup" >Copy</ button >
1047 </ div >
1048 </ div >
1049 < p class = "standup-bluf" >
1050 < span
1051 class = "standup-rag-dot"
1052 role = "img"
1053 aria-label = "Status: {{STANDUP_RAG}}"
1054 ></ span
1055 >{{STANDUP_BLUF}}
1056 </ p >
1057 < div class = "standup-grid" >
1058 < div class = "standup-col yesterday" >
1059 < h3 >{{STANDUP_LABEL_PAST}}</ h3 >
1060 < ul >
1061 {{STANDUP_PAST}}
1062 </ ul >
1063 </ div >
1064 < div class = "standup-col next" >
1065 < h3 >{{STANDUP_LABEL_NEXT}}</ h3 >
1066 < ul >
1067 {{STANDUP_NEXT}}
1068 </ ul >
1069 </ div >
1070 < div class = "standup-col blockers" >
1071 < h3 >Blockers</ h3 >
1072 < ul >
1073 {{STANDUP_BLOCKERS}}
1074 </ ul >
1075 </ div >
1076 </ div >
1077 </ div >
1078
1079 < div class = "toolbar" >
1080 < div class = "filter-group" id = "project-filters" >
1081 < button class = "chip active" data-filter = "all" >
1082 All < span class = "count" id = "ct-all" >0</ span >
1083 </ button >
1084 {{PROJECT_CHIPS}}
1085 </ div >
1086 < div class = "toolbar-divider" id = "status-divider" ></ div >
1087 < div class = "filter-group" id = "status-filters" >
1088 < button class = "chip active" data-status = "all" >All status</ button >
1089 < button class = "chip" data-status = "completed" >● Completed</ button >
1090 < button class = "chip" data-status = "open" >◐ Open</ button >
1091 < button class = "chip" data-status = "deferred" >◇ Deferred</ button >
1092 </ div >
1093 < div class = "toolbar-divider" ></ div >
1094 < div class = "view-toggle" >
1095 < button class = "active" data-view = "projects" >📋 Streams</ button >
1096 < button data-view = "timeline" >🕒 GH Timeline</ button >
1097 </ div >
1098 < input
1099 type = "search"
1100 class = "search"
1101 id = "search"
1102 placeholder = "Filter by file, PR, commit, keyword…"
1103 />
1104 </ div >
1105
1106 < div class = "projects-view" id = "projects-view" >{{PROJECT_SECTIONS}}</ div >
1107
1108 < div class = "timeline" id = "timeline-view" >
1109 < div class = "tl-header" >
1110 {{STAT_GH_EVENTS}} GitHub events in window. Project chips and search
1111 filter this view too. Cross-references to in-session work are shown
1112 with the ⟶ marker. Overnight events sit at the bottom (newest last).
1113 </ div >
1114 < div class = "tl-list" >{{TIMELINE_EVENTS}}</ div >
1115 </ div >
1116
1117 < div class = "theme-block" >
1118 < h2 >Theme of the Day</ h2 >
1119 {{THEME_PARAGRAPHS}}
1120 < div class = "open-threads" >
1121 < strong >Open threads:</ strong > {{OPEN_THREADS}}
1122 </ div >
1123 </ div >
1124
1125 < footer >
1126 < div class = "invocation" >Bitwarden · Engineering Daily Recap</ div >
1127 Generated from Claude Code session archives & GitHub events ·
1128 {{GENERATED_AT}}
1129 </ footer >
1130 </ div >
1131
1132 < script >
1133 ( function () {
1134 const projects = document. querySelectorAll ( ".project" );
1135 const streams = document. querySelectorAll ( ".stream" );
1136 const tlEvents = document. querySelectorAll ( ".tl-event" );
1137 const projectChips = document. querySelectorAll (
1138 "#project-filters .chip" ,
1139 );
1140 const statusChips = document. querySelectorAll ( "#status-filters .chip" );
1141 const statusFilters = document. getElementById ( "status-filters" );
1142 const statusDivider = document. getElementById ( "status-divider" );
1143 const search = document. getElementById ( "search" );
1144 const projectsView = document. getElementById ( "projects-view" );
1145 const timelineView = document. getElementById ( "timeline-view" );
1146 const viewToggleBtns = document. querySelectorAll ( ".view-toggle button" );
1147
1148 const counts = { all: streams. length };
1149 streams. forEach (( s ) => {
1150 const proj = s. closest ( ".project" ).dataset.project;
1151 counts[proj] = (counts[proj] || 0 ) + 1 ;
1152 });
1153 Object. keys (counts). forEach (( k ) => {
1154 const el = document. getElementById ( "ct-" + k);
1155 if (el) el.textContent = counts[k];
1156 });
1157
1158 streams. forEach (( s ) => {
1159 const header = s. querySelector ( ".stream-header" );
1160 header. addEventListener ( "click" , () =>
1161 s.classList. toggle ( "collapsed" ),
1162 );
1163 });
1164
1165 let activeProject = "all" ;
1166 let activeStatus = "all" ;
1167 let activeQuery = "" ;
1168
1169 function applyFilters () {
1170 const q = activeQuery. trim (). toLowerCase ();
1171 projects. forEach (( p ) => {
1172 const proj = p.dataset.project;
1173 let visibleStreams = 0 ;
1174 const projStreams = p. querySelectorAll ( ".stream" );
1175 projStreams. forEach (( s ) => {
1176 const status = s.dataset.status;
1177 const text = s.textContent. toLowerCase ();
1178 const projOk = activeProject === "all" || activeProject === proj;
1179 const statusOk =
1180 activeStatus === "all" || activeStatus === status;
1181 const queryOk = ! q || text. includes (q);
1182 const show = projOk && statusOk && queryOk;
1183 s.classList. toggle ( "hidden" , ! show);
1184 if (show) visibleStreams ++ ;
1185 s. querySelectorAll ( "li" ). forEach (( li ) => {
1186 li.classList. toggle (
1187 "highlight" ,
1188 !! q && li.textContent. toLowerCase (). includes (q),
1189 );
1190 });
1191 });
1192 p.classList. toggle ( "hidden" , visibleStreams === 0 );
1193 });
1194 // Timeline events carry a project but no status, so only project and search apply.
1195 tlEvents. forEach (( ev ) => {
1196 const projOk =
1197 activeProject === "all" || ev.dataset.project === activeProject;
1198 const queryOk = ! q || ev.textContent. toLowerCase (). includes (q);
1199 ev.classList. toggle ( "hidden" , ! (projOk && queryOk));
1200 });
1201 }
1202
1203 projectChips. forEach (( chip ) =>
1204 chip. addEventListener ( "click" , () => {
1205 projectChips. forEach (( c ) => c.classList. remove ( "active" ));
1206 chip.classList. add ( "active" );
1207 activeProject = chip.dataset.filter;
1208 applyFilters ();
1209 }),
1210 );
1211 statusChips. forEach (( chip ) =>
1212 chip. addEventListener ( "click" , () => {
1213 statusChips. forEach (( c ) => c.classList. remove ( "active" ));
1214 chip.classList. add ( "active" );
1215 activeStatus = chip.dataset.status;
1216 applyFilters ();
1217 }),
1218 );
1219 search. addEventListener ( "input" , ( e ) => {
1220 activeQuery = e.target.value;
1221 applyFilters ();
1222 });
1223
1224 viewToggleBtns. forEach (( btn ) =>
1225 btn. addEventListener ( "click" , () => {
1226 viewToggleBtns. forEach (( b ) => b.classList. remove ( "active" ));
1227 btn.classList. add ( "active" );
1228 const view = btn.dataset.view;
1229 projectsView.classList. toggle ( "hidden" , view !== "projects" );
1230 timelineView.classList. toggle ( "active" , view === "timeline" );
1231 statusFilters.classList. toggle ( "hidden" , view === "timeline" );
1232 statusDivider.classList. toggle ( "hidden" , view === "timeline" );
1233 }),
1234 );
1235
1236 // The async Clipboard API can reject (unfocused page, denied permission, or a browser
1237 // that withholds it from file:// pages), so fall back to a selection copy and say so
1238 // plainly if both paths fail.
1239 function legacyCopy ( text ) {
1240 const ta = document. createElement ( "textarea" );
1241 ta.value = text;
1242 ta. setAttribute ( "readonly" , "" );
1243 ta.style.position = "fixed" ;
1244 ta.style.opacity = "0" ;
1245 document.body. appendChild (ta);
1246 ta. select ();
1247 let ok = false ;
1248 try {
1249 ok = document. execCommand ( "copy" );
1250 } catch (err) {
1251 ok = false ;
1252 }
1253 document.body. removeChild (ta);
1254 return ok;
1255 }
1256
1257 const copyBtn = document. getElementById ( "copy-standup" );
1258 function flashCopyState ( label , cls ) {
1259 copyBtn.textContent = label;
1260 copyBtn.classList. add (cls);
1261 setTimeout (() => {
1262 copyBtn.textContent = "Copy" ;
1263 copyBtn.classList. remove (cls);
1264 }, 2000 );
1265 }
1266 const RAG_SHORTCODES = {
1267 green: ":large_green_circle:" ,
1268 amber: ":large_orange_circle:" ,
1269 red: ":red_circle:" ,
1270 };
1271 const collapse = ( s ) => s. replace ( / \s + / g , " " ). trim ();
1272 // Walks a node's children, turning <a> into [text](href) (href's parens
1273 // percent-encoded so they cannot break the markdown), <code> into `text`, and
1274 // any other element into its own children's text, recursively.
1275 function inlineMarkdown ( node ) {
1276 let out = "" ;
1277 node.childNodes. forEach (( child ) => {
1278 if (child.nodeType === Node. TEXT_NODE ) {
1279 out += child.textContent;
1280 } else if (child.tagName === "A" && child. hasAttribute ( "href" )) {
1281 const href = child
1282 . getAttribute ( "href" )
1283 . replace ( / \( / g , "%28" )
1284 . replace ( / \) / g , "%29" );
1285 out += `[${ collapse ( child . textContent ) }](${ href })` ;
1286 } else if (child.tagName === "CODE" ) {
1287 out += "`" + collapse (child.textContent) + "`" ;
1288 } else {
1289 out += inlineMarkdown (child);
1290 }
1291 });
1292 return out;
1293 }
1294 function bulletsFor ( col ) {
1295 const items = Array. from (col. querySelectorAll ( "li" ))
1296 . map (( li ) => collapse ( inlineMarkdown (li)))
1297 . filter (( text ) => text. length > 0 );
1298 return items. length
1299 ? items. map (( text ) => "- " + text). join ( " \n " )
1300 : "- None" ;
1301 }
1302 copyBtn. addEventListener ( "click" , () => {
1303 const standup = document. getElementById ( "standup" );
1304 const shortcode = RAG_SHORTCODES [standup.dataset.rag];
1305 const period = collapse (
1306 document. querySelector ( ".standup-meta" ).textContent,
1307 );
1308 const bluf = collapse (
1309 document. querySelector ( ".standup-bluf" ).textContent,
1310 );
1311 const sections = Array. from (document. querySelectorAll ( ".standup-col" ))
1312 . map (( col ) => {
1313 const label = collapse (col. querySelector ( "h3" ).textContent);
1314 return "`" + label + ":` \n " + bulletsFor (col);
1315 })
1316 . join ( " \n " );
1317 const text = `${ shortcode ? shortcode + " " : ""}${ period } — ${ bluf } \n\n ${ sections }` ;
1318 const viaApi =
1319 navigator.clipboard && window.isSecureContext
1320 ? navigator.clipboard. writeText (text)
1321 : Promise . reject ( new Error ( "Clipboard API unavailable" ));
1322 viaApi
1323 . catch (() => {
1324 if ( ! legacyCopy (text)) throw new Error ( "Copy failed" );
1325 })
1326 . then (
1327 () => flashCopyState ( "Copied ✓" , "copied" ),
1328 () => flashCopyState ( "Copy failed" , "failed" ),
1329 );
1330 });
1331
1332 document. addEventListener ( "keydown" , ( e ) => {
1333 if (e.key === "/" && document.activeElement !== search) {
1334 e. preventDefault ();
1335 search. focus ();
1336 }
1337 if (e.key === "Escape" && document.activeElement === search) {
1338 search.value = "" ;
1339 activeQuery = "" ;
1340 applyFilters ();
1341 search. blur ();
1342 }
1343 });
1344 })();
1345 </ script >
1346 </ body >
1347 </ html >