Jaspers Hub

jaspers / screener-mcp

pluginOfficialReviewed★ 0↓ 33

Jaspers Screener: Screens US SEC registrants by fundamentals, holders and insiders, then reads filings against written criteria with cited quotes.

14 KB
1/* The screener's own CSS. A plugin view is an iframe of its own, so there is no Tailwind here:
2 plain classes, the house style by hand. Colours are the app's theme variables, so the view
3 follows its light and dark, each with its light value as the fallback for an app from before the
4 theme; the screener's own colours get their dark values at the end. Square everything, one grey
5 line, muted secondary text, 13px in the table, and no accent colour but the sign of a number
6 and the verdict of a run, which reuse the same two. */
7
8.sc-root {
9 position: relative;
10 display: flex;
11 flex-direction: column;
12 height: 100%;
13 width: 100%;
14 background: var(--jaspers-background, #fff);
15 color: var(--jaspers-foreground, #171717);
16 font: 13px/1.45 var(--jaspers-font-sans, system-ui, -apple-system, sans-serif);
17}
18
19/* The two bars: filters and counts above, paging and status below. */
20
21.sc-bar {
22 flex: 0 0 auto;
23 display: flex;
24 align-items: center;
25 flex-wrap: wrap;
26 gap: 6px;
27 padding: 6px 8px;
28 border-bottom: 1px solid var(--jaspers-border, #e5e5e5);
29}
30
31.sc-foot {
32 border-bottom: 0;
33 border-top: 1px solid var(--jaspers-border, #e5e5e5);
34}
35
36/* The element's close button sits over the top-right corner. */
37.sc-top {
38 padding-right: 40px;
39}
40
41/* The funnel: the screen as it goes. A row for the universe, one for each filter in its turn, and
42 one for the stage after them, on one grid so the counts line up down the rows: the mark, what the
43 row is, what it left, what it removed, and the first of those by name. It takes the rows it has
44 and scrolls past a share of the view, so a long list of filters leaves the table its room. */
45
46.sc-funnel {
47 flex: 0 0 auto;
48 display: grid;
49 grid-template-columns: max-content minmax(0, max-content) max-content max-content minmax(0, 1fr);
50 column-gap: 10px;
51 max-height: 40%;
52 overflow-y: auto;
53 padding: 4px 8px;
54 border-bottom: 1px solid var(--jaspers-border, #e5e5e5);
55 color: var(--jaspers-muted-foreground, #737373);
56}
57
58.sc-funnel b {
59 color: var(--jaspers-foreground, #171717);
60 font-weight: 600;
61}
62
63.sc-step {
64 grid-column: 1 / -1;
65 display: grid;
66 grid-template-columns: subgrid;
67 align-items: baseline;
68 min-height: 21px;
69}
70
71/* A row's mark: a tick once it is in; a square, filled and pulsing while it is at work, hollow while it is still to come. */
72
73.sc-step-mark {
74 width: 11px;
75 text-align: center;
76}
77
78.sc-step-working .sc-step-mark::before,
79.sc-step-pending .sc-step-mark::before {
80 content: '';
81 display: inline-block;
82 box-sizing: border-box;
83 width: 7px;
84 height: 7px;
85 border: 1px solid #a3a3a3;
86}
87
88.sc-step-working .sc-step-mark::before {
89 border-color: var(--jaspers-foreground, #171717);
90 background: var(--jaspers-foreground, #171717);
91 animation: sc-pulse 1.1s ease-in-out infinite;
92}
93
94.sc-step-what {
95 min-width: 0;
96 overflow: hidden;
97 text-overflow: ellipsis;
98 white-space: nowrap;
99 color: var(--jaspers-foreground, #171717);
100}
101
102.sc-step-pending .sc-step-what {
103 color: #a3a3a3;
104}
105
106.sc-step-n {
107 text-align: right;
108 white-space: nowrap;
109 font-variant-numeric: tabular-nums;
110}
111
112/* What a row at work says, and the whole of what the stage after the filters says, take the rest of the row. */
113
114.sc-step-note,
115.sc-step-run {
116 grid-column: 3 / -1;
117 min-width: 0;
118}
119
120.sc-step-run {
121 display: flex;
122 align-items: center;
123 flex-wrap: wrap;
124 gap: 0 10px;
125}
126
127.sc-step-note {
128 white-space: nowrap;
129 font-variant-numeric: tabular-nums;
130}
131
132/* The names: as many as the row has room for, and how many more there are, which stays in sight. */
133
134.sc-step-names,
135.sc-step-verdict {
136 display: flex;
137 gap: 6px;
138 min-width: 0;
139 white-space: nowrap;
140}
141
142/* Who passed and who failed share what the row has left, and go under it when that is too little. */
143.sc-step-verdict {
144 flex: 1 1 14ch;
145}
146
147.sc-step-verdict b {
148 color: inherit;
149}
150
151.sc-names {
152 min-width: 0;
153 overflow: hidden;
154 text-overflow: ellipsis;
155}
156
157.sc-names-more {
158 flex: 0 0 auto;
159}
160
161/* The numbers of a row arrive; they do not blink in. */
162
163.sc-step-done > .sc-step-n,
164.sc-step-done > .sc-step-names {
165 animation: sc-in 0.28s ease-out;
166}
167
168@keyframes sc-in {
169 from {
170 opacity: 0;
171 transform: translateY(-2px);
172 }
173}
174
175@keyframes sc-pulse {
176 50% {
177 opacity: 0.25;
178 }
179}
180
181/* How far a run is: one bar cut into what was found and what is still to be. The track is what waits. */
182
183.sc-meter {
184 flex: 0 0 auto;
185 display: inline-flex;
186 gap: 1px;
187 width: 120px;
188 height: 8px;
189 background: var(--jaspers-border, #e5e5e5);
190}
191
192.sc-seg {
193 display: block;
194 min-width: 2px;
195}
196
197.sc-seg-pass {
198 background: var(--jaspers-positive, #15803d);
199}
200
201.sc-seg-fail {
202 background: var(--jaspers-negative, #b91c1c);
203}
204
205.sc-seg-unclear {
206 background: #a16207;
207}
208
209.sc-seg-unverified {
210 background: var(--jaspers-muted-foreground, #737373);
211}
212
213/* An error is not a fail: the same red, hatched. */
214.sc-seg-error {
215 background: repeating-linear-gradient(135deg, var(--jaspers-destructive, #dc2626) 0 2px, transparent 2px 4px);
216}
217
218.sc-seg-fetched,
219.sc-seg-running {
220 background: var(--jaspers-foreground, #171717);
221}
222
223.sc-seg-running {
224 animation: sc-pulse 1.1s ease-in-out infinite;
225}
226
227.sc-btn {
228 border: 1px solid var(--jaspers-border, #e5e5e5);
229 background: var(--jaspers-background, #fff);
230 color: var(--jaspers-foreground, #171717);
231 padding: 2px 8px;
232 cursor: pointer;
233}
234
235.sc-btn:hover:not(:disabled) {
236 background: var(--jaspers-muted, #f5f5f5);
237}
238
239.sc-btn:disabled {
240 color: #a3a3a3;
241 cursor: default;
242}
243
244.sc-chip {
245 display: inline-flex;
246 align-items: center;
247 gap: 4px;
248 border: 1px solid var(--jaspers-border, #e5e5e5);
249 background: var(--jaspers-muted, #fafafa);
250 padding: 1px 3px 1px 6px;
251 white-space: nowrap;
252}
253
254.sc-chip-label {
255 border: 0;
256 background: none;
257 color: inherit;
258 font: inherit;
259 padding: 0;
260 cursor: pointer;
261}
262
263.sc-chip-x {
264 border: 0;
265 background: none;
266 color: var(--jaspers-muted-foreground, #737373);
267 cursor: pointer;
268 padding: 0 3px;
269 line-height: 1;
270}
271
272.sc-chip-x:hover:not(:disabled) {
273 color: var(--jaspers-foreground, #171717);
274}
275
276.sc-chip-x:disabled {
277 cursor: default;
278}
279
280.sc-input {
281 border: 1px solid var(--jaspers-border, #e5e5e5);
282 background: var(--jaspers-background, #fff);
283 color: var(--jaspers-foreground, #171717);
284 padding: 2px 6px;
285 outline: none;
286}
287
288.sc-input:focus {
289 border-color: var(--jaspers-foreground, #171717);
290}
291
292.sc-count {
293 margin-left: auto;
294 color: var(--jaspers-muted-foreground, #737373);
295 white-space: nowrap;
296}
297
298.sc-status {
299 color: var(--jaspers-muted-foreground, #737373);
300 white-space: nowrap;
301}
302
303.sc-error {
304 color: var(--jaspers-destructive, #dc2626);
305 white-space: normal;
306}
307
308/* The table fills what is left and scrolls both ways; nothing else on the page moves. */
309
310.sc-table-wrap {
311 flex: 1 1 auto;
312 overflow: auto;
313 transition: opacity 0.12s;
314}
315
316/* The rows of the list before the one that is on its way. */
317.sc-stale {
318 opacity: 0.5;
319}
320
321.sc-table {
322 border-collapse: collapse;
323 font-size: 13px;
324 width: max-content;
325 min-width: 100%;
326}
327
328.sc-table th,
329.sc-table td {
330 border-bottom: 1px solid var(--jaspers-border, #e5e5e5);
331 padding: 3px 8px;
332 white-space: nowrap;
333}
334
335.sc-table th {
336 position: sticky;
337 top: 0;
338 z-index: 1;
339 background: var(--jaspers-background, #fff);
340 text-align: left;
341 font-weight: 500;
342 color: var(--jaspers-muted-foreground, #737373);
343 padding: 0;
344}
345
346.sc-sort {
347 width: 100%;
348 border: 0;
349 background: none;
350 color: inherit;
351 font: inherit;
352 text-align: right;
353 padding: 4px 8px;
354 cursor: pointer;
355}
356
357.sc-sort:hover {
358 color: var(--jaspers-foreground, #171717);
359}
360
361.sc-num {
362 text-align: right;
363 font-variant-numeric: tabular-nums;
364}
365
366.sc-up {
367 color: var(--jaspers-positive, #15803d);
368}
369
370.sc-down {
371 color: var(--jaspers-negative, #b91c1c);
372}
373
374/* The company column stays put while the numbers scroll past it. */
375
376.sc-company {
377 position: sticky;
378 left: 0;
379 background: var(--jaspers-background, #fff);
380 z-index: 2;
381 padding: 3px 8px;
382 text-align: left;
383 min-width: 190px;
384}
385
386th.sc-company {
387 z-index: 3;
388 padding: 4px 8px;
389}
390
391.sc-ticker {
392 font-weight: 600;
393}
394
395.sc-name {
396 display: block;
397 color: var(--jaspers-muted-foreground, #737373);
398 max-width: 210px;
399 overflow: hidden;
400 text-overflow: ellipsis;
401}
402
403.sc-table tbody tr:hover td {
404 background: var(--jaspers-muted, #f5f5f5);
405}
406
407.sc-empty {
408 padding: 12px;
409 color: var(--jaspers-muted-foreground, #737373);
410}
411
412/* The filter dialog: a panel over the view, not a portal and not a window. */
413
414.sc-dialog {
415 position: absolute;
416 inset: 0;
417 z-index: 10;
418 display: flex;
419 align-items: flex-start;
420 justify-content: center;
421 padding: 10px;
422 background: rgba(23, 23, 23, 0.15);
423}
424
425.sc-dialog-panel {
426 display: flex;
427 flex-direction: column;
428 width: 100%;
429 max-width: 760px;
430 max-height: 100%;
431 background: var(--jaspers-background, #fff);
432 border: 1px solid var(--jaspers-border, #e5e5e5);
433 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
434}
435
436.sc-dialog-head,
437.sc-dialog-foot {
438 flex: 0 0 auto;
439 display: flex;
440 align-items: center;
441 gap: 8px;
442 padding: 6px 10px;
443}
444
445.sc-dialog-head {
446 border-bottom: 1px solid var(--jaspers-border, #e5e5e5);
447 font-weight: 600;
448}
449
450.sc-dialog-foot {
451 border-top: 1px solid var(--jaspers-border, #e5e5e5);
452 justify-content: flex-end;
453}
454
455.sc-dialog-body {
456 flex: 1 1 auto;
457 overflow: auto;
458 padding: 10px;
459}
460
461.sc-section {
462 margin-bottom: 12px;
463}
464
465.sc-label {
466 display: block;
467 margin-bottom: 4px;
468 color: var(--jaspers-muted-foreground, #737373);
469 font-size: 11px;
470 text-transform: uppercase;
471 letter-spacing: 0.04em;
472}
473
474.sc-check {
475 display: inline-flex;
476 align-items: center;
477 gap: 5px;
478 cursor: pointer;
479 user-select: none;
480}
481
482.sc-check input {
483 accent-color: var(--jaspers-primary, #171717);
484}
485
486/* One filter to a row: the field, how it is compared, the value in the unit the user types. */
487
488.sc-filter {
489 margin-bottom: 6px;
490}
491
492.sc-filter-row {
493 display: flex;
494 align-items: center;
495 flex-wrap: wrap;
496 gap: 4px;
497}
498
499.sc-filter-field {
500 flex: 0 1 230px;
501 min-width: 0;
502}
503
504.sc-filter-value {
505 flex: 1 1 110px;
506 min-width: 0;
507}
508
509.sc-hint {
510 margin: 2px 0 0;
511 color: var(--jaspers-muted-foreground, #737373);
512 font-size: 12px;
513}
514
515.sc-filter .sc-error,
516.sc-dialog-body > .sc-error {
517 margin: 2px 0 0;
518}
519
520.sc-tickers {
521 width: 100%;
522 resize: vertical;
523 font: inherit;
524}
525
526.sc-columns {
527 display: grid;
528 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
529 gap: 2px 16px;
530}
531
532.sc-unit {
533 color: #a3a3a3;
534 white-space: nowrap;
535}
536
537/* The question chip carries a sentence; its text truncates and its cross stays. */
538.sc-chip.sc-q {
539 display: inline-flex;
540 align-items: center;
541 max-width: 480px;
542 min-width: 0;
543}
544.sc-q-text {
545 min-width: 0;
546 overflow: hidden;
547 text-overflow: ellipsis;
548 white-space: nowrap;
549}
550
551/* The qualitative stage: the criteria and their input on the left, the run on the right. */
552
553.sc-q-input {
554 flex: 1 1 220px;
555 min-width: 0;
556}
557
558.sc-run,
559.sc-verdicts {
560 display: inline-flex;
561 align-items: center;
562 flex-wrap: wrap;
563 gap: 6px;
564}
565
566.sc-run {
567 margin-left: auto;
568}
569
570.sc-primary {
571 border-color: var(--jaspers-foreground, #171717);
572 font-weight: 600;
573}
574
575.sc-toggle.sc-on {
576 border-color: currentColor;
577 background: var(--jaspers-muted, #f5f5f5);
578}
579
580/* A verdict is the one other thing with a colour: the sign's two, an amber, and the muted grey. */
581
582.sc-v-pass {
583 color: var(--jaspers-positive, #15803d);
584}
585
586.sc-v-fail {
587 color: var(--jaspers-negative, #b91c1c);
588}
589
590.sc-v-unclear {
591 color: #a16207;
592}
593
594.sc-v-unverified {
595 color: var(--jaspers-muted-foreground, #737373);
596 font-style: italic;
597}
598
599.sc-table th.sc-verdict-head {
600 padding: 4px 8px;
601 max-width: 140px;
602 overflow: hidden;
603 text-overflow: ellipsis;
604}
605
606.sc-verdict {
607 text-align: left;
608}
609
610.sc-mark {
611 color: var(--jaspers-destructive, #dc2626);
612 font-weight: 600;
613 cursor: help;
614}
615
616/* A company being read: a square for each step the server takes it through, the ones behind it filled, the one it is in pulsing. */
617
618.sc-rail {
619 display: inline-flex;
620 align-items: center;
621 gap: 2px;
622}
623
624.sc-rail i {
625 box-sizing: border-box;
626 width: 6px;
627 height: 6px;
628 border: 1px solid #a3a3a3;
629}
630
631.sc-rail i:last-of-type {
632 margin-right: 4px;
633}
634
635.sc-rail .sc-rail-done {
636 border-color: var(--jaspers-muted-foreground, #737373);
637 background: var(--jaspers-muted-foreground, #737373);
638}
639
640.sc-rail .sc-rail-now {
641 border-color: var(--jaspers-foreground, #171717);
642 background: var(--jaspers-foreground, #171717);
643 animation: sc-pulse 1.1s ease-in-out infinite;
644}
645
646/* A ticker with a run's answer behind it opens that company's evidence. */
647
648.sc-ticker-open {
649 border: 0;
650 border-bottom: 1px dotted var(--jaspers-muted-foreground, #737373);
651 background: none;
652 color: inherit;
653 font: inherit;
654 font-weight: 600;
655 padding: 0;
656 cursor: pointer;
657}
658
659.sc-table tbody tr.sc-row-open td {
660 background: var(--jaspers-muted, #f5f5f5);
661}
662
663/* The evidence pane: under the table, as tall as it needs up to under half the view. */
664
665.sc-evidence {
666 flex: 0 1 auto;
667 display: flex;
668 flex-direction: column;
669 max-height: 45%;
670 min-height: 80px;
671 border-top: 1px solid var(--jaspers-border, #e5e5e5);
672}
673
674.sc-evidence-head {
675 flex: 0 0 auto;
676 display: flex;
677 align-items: center;
678 gap: 8px;
679 padding: 4px 8px;
680 border-bottom: 1px solid var(--jaspers-border, #e5e5e5);
681}
682
683.sc-evidence-body {
684 flex: 1 1 auto;
685 overflow: auto;
686 padding: 6px 10px 10px;
687}
688
689.sc-evidence-body p {
690 margin: 0 0 4px;
691 white-space: normal;
692}
693
694.sc-criterion {
695 padding: 6px 0;
696 border-bottom: 1px solid var(--jaspers-border, #e5e5e5);
697}
698
699.sc-criterion:last-child {
700 border-bottom: 0;
701}
702
703.sc-question {
704 font-weight: 600;
705}
706
707.sc-cite {
708 margin: 6px 0;
709 padding: 4px 8px;
710 border-left: 2px solid var(--jaspers-border, #e5e5e5);
711 background: var(--jaspers-muted, #fafafa);
712}
713
714.sc-cite .sc-quote {
715 margin: 0 0 2px;
716}
717
718.sc-cite .sc-cite-foot {
719 margin: 0;
720}
721
722.sc-cite-foot {
723 display: flex;
724 flex-wrap: wrap;
725 gap: 2px 10px;
726 color: var(--jaspers-muted-foreground, #737373);
727 font-size: 12px;
728}
729
730.sc-link {
731 border: 0;
732 background: none;
733 color: var(--jaspers-foreground, #171717);
734 font: inherit;
735 padding: 0;
736 text-decoration: underline;
737 cursor: pointer;
738}
739
740/* Dark values for the screener's own colours: faint text a step under the muted, and, on a page
741 already near black, a backdrop with more black to dim it and a shadow with more to show. */
742@media (prefers-color-scheme: dark) {
743 .sc-btn:disabled,
744 .sc-unit,
745 .sc-step-pending .sc-step-what {
746 color: #737373;
747 }
748
749 .sc-step-pending .sc-step-mark::before,
750 .sc-rail i {
751 border-color: #737373;
752 }
753
754 .sc-v-unclear {
755 color: #eab308;
756 }
757
758 .sc-seg-unclear {
759 background: #eab308;
760 }
761
762 .sc-dialog {
763 background: rgba(0, 0, 0, 0.5);
764 }
765
766 .sc-dialog-panel {
767 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
768 }
769}
770
771/* Someone who asked for less motion gets the same states, standing still. */
772@media (prefers-reduced-motion: reduce) {
773 .sc-step-working .sc-step-mark::before,
774 .sc-seg-running,
775 .sc-rail .sc-rail-now,
776 .sc-step-done > .sc-step-n,
777 .sc-step-done > .sc-step-names {
778 animation: none;
779 }
780
781 .sc-table-wrap {
782 transition: none;
783 }
784}