clearotron 0.3.2-beta.2 → 0.3.2-beta.4

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 (54) hide show
  1. package/.env.example +10 -0
  2. package/INSTALL.md +2 -0
  3. package/README.md +15 -10
  4. package/bin/onboard.mjs +4 -4
  5. package/build-info.json +2 -2
  6. package/docs/CLIENT-MCP.md +54 -54
  7. package/docs/DELIVERY.md +16 -15
  8. package/docs/E2E.md +8 -8
  9. package/docs/GLOSSARY.md +2 -2
  10. package/docs/INTAKE.md +11 -11
  11. package/docs/ONBOARDING.md +17 -16
  12. package/docs/PORTAL.md +18 -16
  13. package/docs/README.md +10 -10
  14. package/docs/SECURITY.md +20 -20
  15. package/docs/architecture/01-product-overview.md +17 -16
  16. package/docs/architecture/02-architecture.md +6 -6
  17. package/docs/architecture/03-run-lifecycle.md +7 -7
  18. package/docs/architecture/04-configuration-reference.md +14 -13
  19. package/docs/architecture/05-config-governance.md +30 -29
  20. package/docs/architecture/05-customer-profiles.md +35 -35
  21. package/docs/architecture/06-operations-runbook.md +20 -20
  22. package/docs/architecture/07-quality-and-audit.md +17 -17
  23. package/docs/architecture/08-development-guide.md +3 -3
  24. package/docs/architecture/09-security-and-data.md +27 -27
  25. package/docs/architecture/README.md +1 -1
  26. package/docs/branding.md +8 -3
  27. package/docs/configuration.md +18 -18
  28. package/docs/writing-standard.md +3 -3
  29. package/driver/CHANGELOG.md +36 -0
  30. package/driver/package.json +1 -1
  31. package/driver/portal-service.mjs +34 -4
  32. package/driver/suite-census.json +114 -48
  33. package/mcp-server/CHANGELOG.md +8 -0
  34. package/mcp-server/package.json +1 -1
  35. package/mcp-server/packs/client/CONNECT.md +6 -6
  36. package/package.json +1 -1
  37. package/portal-ui/dist/assets/{index-BsbasHjM.js → index-5UyqAyNM.js} +3384 -2973
  38. package/portal-ui/dist/assets/{index-DNQpLYZF.css → index-CVOIvdhc.css} +992 -205
  39. package/portal-ui/dist/index.html +2 -2
  40. package/portal-ui/package.json +1 -1
  41. package/providers/_shared/script-form.mjs +2 -2
  42. package/providers/clarivate/src/core.js +18 -27
  43. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  44. package/providers/oauth-mcp-bridge/package.json +1 -1
  45. package/providers/signa/src/core.js +3 -3
  46. package/scripts/ask-ai-render-check.mjs +287 -105
  47. package/scripts/env-classify.mjs +6 -0
  48. package/scripts/release-note-required.mjs +16 -2
  49. package/scripts/settings-render-check.mjs +575 -0
  50. package/shared/brand.mjs +29 -0
  51. package/shared/connect-clients.mjs +99 -47
  52. package/shared/names-in-force.mjs +1 -0
  53. package/shared/stdio-connect.mjs +16 -2
  54. package/shared/writing-standard-classes.mjs +34 -3
@@ -254,6 +254,36 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
254
254
  border-bottom: 1px solid var(--border-hairline);
255
255
  }
256
256
 
257
+ /* The breadcrumb above it: one step back as a quiet button, then where the reader is. Compound selectors
258
+ throughout this block, because the button and row rules they adjust are declared later in the file. */
259
+ .report-crumbs { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
260
+ .btn-ghost.report-back { padding: 4px 9px; border-color: var(--border-hairline); }
261
+ .report-crumb { font-size: 12.5px; color: var(--text-muted); }
262
+
263
+ /* THE HEADER'S TWO ACTIONS, Ask AI then Export, as secondary buttons with a hairline edge. */
264
+ .btn-ghost.report-action,
265
+ .btn-ghost.ask-ai-btn { border-color: var(--border-hairline); }
266
+
267
+ /* ── ASK AI ──────────────────────────────────────────────────────────────────────────────────────────
268
+ The panel hangs under its button with the right edges aligned, wherever the button sits — the report's
269
+ header, or a row where a bordered primary action beside it wants the quieter button. */
270
+ .ask-ai { position: relative; }
271
+ .btn-ghost.ask-ai-btn.is-quiet { border-color: transparent; color: var(--text-muted); }
272
+ .btn-ghost.ask-ai-btn.is-quiet:hover { border-color: var(--border-hairline); color: var(--text-strong); }
273
+ .ask-ai-float { position: absolute; right: 0; top: calc(100% + 6px); z-index: 50; }
274
+ .ask-ai-panel { width: 360px; max-width: calc(100vw - 32px); padding: 12px; display: grid; gap: 10px; text-align: left; }
275
+ .ask-ai-head { font-size: 12.5px; line-height: 1.45; color: var(--text-muted); }
276
+ .ask-ai-when { white-space: nowrap; }
277
+ .ask-ai-choices { display: grid; gap: 1px; }
278
+ .nav-item.ask-ai-choice {
279
+ gap: 10px; align-items: flex-start; margin: 0; padding: 7px 9px;
280
+ font-size: 13px; line-height: 1.45; white-space: normal; color: var(--text-body);
281
+ }
282
+ .nav-item.ask-ai-choice[aria-checked='true'] { background: var(--accent-wash); color: var(--text-strong); font-weight: 700; }
283
+ .ask-ai-choice .radio { margin-top: 2px; }
284
+ .btn-primary.ask-ai-go { justify-self: start; padding: 8px 14px; font-size: 13px; }
285
+ .ask-ai-note { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-muted); }
286
+
257
287
  /* ── sidebar ─────────────────────────────────────────────────────────────────────────────────────── */
258
288
 
259
289
  .sidebar {
@@ -402,6 +432,23 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
402
432
  color: var(--text-strong);
403
433
  }
404
434
 
435
+ /* The avatar: the signed-in person's initials, and the door to the screens that belong to them. Drawn
436
+ active on those screens, because the rail cannot highlight a screen it does not carry. After the hover
437
+ rule and one class stronger, so hovering the round button does not repaint it as a square one's. */
438
+ .icon-btn.avatar {
439
+ border-radius: 50%;
440
+ background: var(--surface-float);
441
+ border: 1px solid var(--border-hairline);
442
+ font-size: 11px;
443
+ font-weight: 700;
444
+ }
445
+
446
+ .icon-btn.avatar[aria-current='true'] {
447
+ background: var(--accent-wash);
448
+ border-color: var(--accent);
449
+ color: var(--text-accent);
450
+ }
451
+
405
452
  .float {
406
453
  background: var(--surface-float);
407
454
  border: 1px solid var(--border-strong);
@@ -518,45 +565,42 @@ table.data.fixed td {
518
565
  overflow-wrap: anywhere;
519
566
  }
520
567
 
568
+ /* BELOW PHONE WIDTH A FIXED TABLE HAS NOTHING LEFT TO DIVIDE, so it stops dividing and scrolls instead.
569
+ Fixed layout shares the container out as percentages, which is what lets this table fit any desktop
570
+ width without a scrollbar. On a phone the shares become slivers: measured at 400px, the Risk word
571
+ printed through the Updated date, ordinary marks broke mid-word, and the count pills rendered as
572
+ circles with their text outside them. The page itself then scrolled sideways, because the row's own
573
+ controls are wider than the column they were given.
574
+ Under this breakpoint the table becomes an ordinary content-sized one — which is what `table.data`
575
+ already is, floor included, so no width is invented here — and its wrapper, which has always had
576
+ `overflow-x: auto`, does the scrolling. A table a reader drags sideways is readable; one printed
577
+ through itself is not.
578
+ THE FLOOR IS RESTORED ONLY HERE, and that is the whole point. `table.data.fixed` sets `min-width: 0`
579
+ above, because a floor on a nearly-wide-enough container is what puts a 19px scrollbar on a
580
+ client-facing page for no reason. That argument is about desktop widths and it still holds there. */
581
+ @media (max-width: 560px) {
582
+ table.data.fixed {
583
+ table-layout: auto;
584
+ min-width: 860px;
585
+ }
586
+ }
587
+
521
588
  /* An expanded read: quiet enough to read as subordinate to the row it belongs to. The indent is on the
522
589
  cells, not here, because it is structural — see ReadRow. */
523
590
  table.data tr.read-row > td {
524
- background: var(--surface-sunken);
591
+ background-color: var(--surface-sunken);
525
592
  }
526
593
 
527
- /* the whole read row opens its report.
528
- The affordance is on the thing you can click, across the WHOLE row, which is the point: the only way
529
- in used to be a text button at the far right edge, furthest from the run title the reader has just
530
- read, on a row with `cursor: auto` and no hover state at all. People concluded there was no report.
531
- A read with no report gets none of this — not the pointer, not the hover, not the focus ring. A dead
532
- target is worse than no target. */
594
+ /* A search with a report can still be opened with a click anywhere on its row — the mouse's convenience.
595
+ The named, focusable control is its Open button in the actions column; the row takes no focus of its
596
+ own, so a keyboard meets one target per search rather than two.
597
+ A search with no report gets none of this — not the pointer, not the hover. A dead target is worse
598
+ than no target. */
533
599
  table.data tr.read-row.openable > td {
534
600
  cursor: pointer;
535
601
  }
536
602
  table.data tr.read-row.openable:hover > td {
537
- background: var(--surface-raised);
538
- }
539
- /* focus-visible, not focus: a keyboard user must see where they are, a mouse user must not get a ring
540
- they did not ask for. The outline is on the ROW, so it reads as one target rather than six cells. */
541
- table.data tr.read-row.openable:focus {
542
- outline: none;
543
- }
544
- table.data tr.read-row.openable:focus-visible {
545
- outline: 2px solid var(--accent);
546
- outline-offset: -2px;
547
- }
548
- /* The cue that the row leads somewhere. It brightens with the row rather than being a control of its
549
- own — clicking it is clicking the row, because it is not clickable separately. */
550
- .read-go {
551
- display: inline-flex;
552
- align-items: center;
553
- gap: 4px;
554
- font-size: 12.5px;
555
- color: var(--text-faint);
556
- }
557
- table.data tr.read-row.openable:hover .read-go,
558
- table.data tr.read-row.openable:focus-visible .read-go {
559
- color: var(--accent);
603
+ background-color: var(--surface-raised);
560
604
  }
561
605
 
562
606
  table.data tr.row {
@@ -606,8 +650,14 @@ button.twisty:focus-visible {
606
650
  white-space: nowrap;
607
651
  }
608
652
 
609
- table.data tr.row:hover td {
610
- background: var(--surface-sunken);
653
+ table.data tr.row:not(.inert):hover td {
654
+ background-color: var(--surface-sunken);
655
+ }
656
+
657
+ /* A name with one search and no report — running for the first time — has nothing to open and nothing to
658
+ expand, so its row does not pretend to be a control. */
659
+ table.data tr.row.inert {
660
+ cursor: default;
611
661
  }
612
662
 
613
663
  /* A SECTION HEADER, not a decorative divider.
@@ -617,7 +667,9 @@ table.data tr.row:hover td {
617
667
  Body-text size, medium weight, full-strength ink, sentence case rather than letterspaced caps — and a
618
668
  hairline rule spanning the table beneath it, so containment is visible without reading anything. */
619
669
  table.data tr.group-head td {
620
- padding: 22px 12px 7px;
670
+ padding: 10px 12px;
671
+ background: var(--surface-sunken);
672
+ border-top: 1px solid var(--border-strong);
621
673
  border-bottom: 1px solid var(--border-strong);
622
674
  }
623
675
 
@@ -635,10 +687,177 @@ table.data tr.group-head .owner-count {
635
687
  color: var(--text-muted);
636
688
  }
637
689
 
638
- /* Child rows sit one step in from their owner, so the containment reads structurally rather than by
639
- proximity alone. On the first cell only — indenting every cell would move the columns. */
640
- table.data tr.group-head ~ tr.row > td:first-child {
641
- padding-left: 22px;
690
+ /* NESTING IS DRAWN, not only indented. The company heading is a filled band; a name inside a group carries
691
+ one vertical rule in its first cell, and a search under a name one rule per level above it. Drawn as
692
+ background images so a rule costs no width and cannot move a column, and so it survives the row's own
693
+ background colour. */
694
+ table.data td.nest-1,
695
+ table.data td.nest-2 {
696
+ background-repeat: no-repeat;
697
+ }
698
+ table.data td.nest-1 {
699
+ background-image: linear-gradient(var(--border-strong), var(--border-strong));
700
+ background-size: 1px 100%;
701
+ background-position: 7px 0;
702
+ }
703
+ table.data td.nest-2 {
704
+ background-image: linear-gradient(var(--border-strong), var(--border-strong)), linear-gradient(var(--border-strong), var(--border-strong));
705
+ background-size: 1px 100%, 1px 100%;
706
+ background-position: 7px 0, 17px 0;
707
+ }
708
+
709
+ /* "Group ·" before a group's name: what kind of row this is, quieter than the name it introduces. */
710
+ .row-kind {
711
+ margin-right: 6px;
712
+ font-size: 13px;
713
+ color: var(--text-muted);
714
+ }
715
+
716
+ /* ── the Clearances actions column ──────────────────────────────────────────────────────────────────
717
+ Open, Ask AI and the row menu in fixed tracks, so every Open on the page shares one x and one width.
718
+ Narrow, Ask AI sits under Open; with room, beside it — the table's wrapper is the container measured,
719
+ so a collapsed rail widens it exactly as a wider window does. */
720
+ .names-wrap {
721
+ container-type: inline-size;
722
+ /* VISIBLE, not the wrapper's usual `auto`. This table cannot overflow — its columns are shares of its
723
+ own width — so the scroll container bought nothing here, and it clipped on both axes: an Ask AI
724
+ panel opened from a row near the bottom of the list was cut off at the table's edge. */
725
+ overflow: visible;
726
+ }
727
+ /* Eight pixels either side instead of twelve, on every column but the two narrow ones at the left: the
728
+ actions column takes a quarter of the table, and the text columns keep their words on one line by
729
+ giving up padding rather than width. The date and the actions give up a little more. */
730
+ .names-wrap table.data.fixed th:nth-child(n + 3),
731
+ .names-wrap table.data.fixed td:nth-child(n + 3) {
732
+ padding-left: 8px;
733
+ padding-right: 8px;
734
+ }
735
+ .names-wrap table.data.fixed td.col-date,
736
+ .names-wrap table.data.fixed td.col-actions {
737
+ padding-left: 6px;
738
+ padding-right: 6px;
739
+ }
740
+ /* A status that does not fit wraps between its halves — "Queued ·" over "1 search" — rather than running
741
+ under the Risk column: `.status` is nowrap everywhere else, which is right for a word and wrong for a
742
+ phrase in a column of fixed width. The dot stays level with the first line. */
743
+ .names-wrap table.data.fixed td .status {
744
+ white-space: normal;
745
+ align-items: flex-start;
746
+ }
747
+ .names-wrap table.data.fixed td .status .dot {
748
+ margin-top: 0.42em;
749
+ }
750
+ .status-count {
751
+ white-space: nowrap;
752
+ }
753
+ /* A date is one word to a reader. A line may break before it, never at its hyphens: "2026-09-" over "02"
754
+ read as a different date on the line below. */
755
+ .names-wrap table.data.fixed td .sub.report-line {
756
+ overflow-wrap: normal;
757
+ }
758
+ .names-wrap .nowrap-date {
759
+ white-space: nowrap;
760
+ }
761
+ .row-actions {
762
+ /* WIDE ENOUGH FOR THE LONGEST LABEL ON A FONT THIS BOX DOES NOT HAVE. The track is fixed so every
763
+ Open shares one width — "the track decides, not the text" — which means the number has to clear
764
+ the longest label, "Open latest report", everywhere. At 11em it cleared it on a developer machine
765
+ and clipped on the CI runner, whose default font is wider: the browser check went green locally
766
+ and red in CI on the same commit. 13.5em is the local measurement plus about a fifth, which is
767
+ more than the spread between the font stacks the two see. */
768
+ --row-open-w: 13.5em;
769
+ --row-ask-w: 6.5em;
770
+ display: grid;
771
+ grid-template-columns: var(--row-open-w) auto;
772
+ grid-template-areas: 'open more' 'ask more';
773
+ justify-content: start;
774
+ align-items: start;
775
+ gap: 6px;
776
+ font-size: 12.5px;
777
+ }
778
+ .row-actions-open { grid-area: open; }
779
+ /* Ask AI in a row: the header's button, compacted to the height of Open beside it and kept on one line —
780
+ in its own track it wrapped to "Ask" over "AI" with its icon stranded beside them. */
781
+ .row-actions .btn-ghost.ask-ai-btn {
782
+ padding: 5px 8px;
783
+ gap: 5px;
784
+ white-space: nowrap;
785
+ }
786
+ .row-actions-ask { grid-area: ask; }
787
+ .row-actions-more { grid-area: more; }
788
+ .row-actions-open:empty,
789
+ .row-actions-ask:empty {
790
+ display: none;
791
+ }
792
+ @container (min-width: 1000px) {
793
+ .row-actions {
794
+ grid-template-columns: var(--row-open-w) var(--row-ask-w) auto;
795
+ grid-template-areas: 'open ask more';
796
+ }
797
+ .row-actions-open:empty,
798
+ .row-actions-ask:empty {
799
+ display: block;
800
+ }
801
+ }
802
+ /* The bordered Open. One width, whatever its label says: the track decides, not the text. */
803
+ button.row-open {
804
+ width: 100%;
805
+ padding: 5px 8px;
806
+ border: 1px solid var(--border-strong);
807
+ border-radius: 7px;
808
+ background: var(--surface-raised);
809
+ color: var(--text-strong);
810
+ font: inherit;
811
+ font-weight: 600;
812
+ white-space: nowrap;
813
+ cursor: pointer;
814
+ }
815
+ button.row-open:hover {
816
+ border-color: var(--text-muted);
817
+ }
818
+ button.row-open:focus-visible,
819
+ button.row-menu-btn:focus-visible {
820
+ outline: 2px solid var(--accent);
821
+ outline-offset: 1px;
822
+ }
823
+ .row-menu {
824
+ position: relative;
825
+ }
826
+ button.row-menu-btn {
827
+ width: 28px;
828
+ height: 28px;
829
+ padding: 0;
830
+ border: 0;
831
+ border-radius: 6px;
832
+ background: none;
833
+ color: var(--text-muted);
834
+ font: inherit;
835
+ font-size: 15px;
836
+ line-height: 1;
837
+ cursor: pointer;
838
+ }
839
+ button.row-menu-btn:hover,
840
+ button.row-menu-btn[aria-expanded='true'] {
841
+ background: var(--surface-sunken);
842
+ color: var(--text-strong);
843
+ }
844
+ /* To the LEFT of the button, level with it. Every row menu holds one act, and a list of one fits beside
845
+ its button inside its own row — so opening it covers the row it acts on, never the rows beneath, and
846
+ it cannot be the thing that runs off the bottom of the page. */
847
+ .row-menu-list {
848
+ position: absolute;
849
+ right: 100%;
850
+ top: -4px;
851
+ margin-right: 4px;
852
+ min-width: 132px;
853
+ padding: 4px;
854
+ z-index: 50;
855
+ }
856
+ button.row-menu-item {
857
+ font-size: 13px;
858
+ padding: 6px 10px;
859
+ margin: 0;
860
+ white-space: nowrap;
642
861
  }
643
862
 
644
863
  .pager {
@@ -691,6 +910,21 @@ table.data tr.group-head ~ tr.row > td:first-child {
691
910
  color: var(--text-muted);
692
911
  }
693
912
 
913
+ /* The larger pill, for a control that is a card's closing action rather than a label — and an anchor
914
+ drawn as one, which is what opening a document in a new tab is. */
915
+ .pill-lg {
916
+ padding: 5px 13px;
917
+ font-size: 12px;
918
+ }
919
+ a.pill {
920
+ text-decoration: none;
921
+ cursor: pointer;
922
+ }
923
+ a.pill:hover {
924
+ color: var(--text-strong);
925
+ border-color: var(--border-strong);
926
+ }
927
+
694
928
  /* ── status + risk ───────────────────────────────────────────────────────────────────────────────── */
695
929
 
696
930
  .dot {
@@ -707,7 +941,7 @@ table.data tr.group-head ~ tr.row > td:first-child {
707
941
  }
708
942
 
709
943
  .dot.running {
710
- background: var(--accent);
944
+ background: var(--text-muted);
711
945
  animation: cordPulse 1.4s ease-in-out infinite;
712
946
  }
713
947
 
@@ -862,6 +1096,270 @@ input.filter {
862
1096
  background: var(--surface-raised);
863
1097
  }
864
1098
 
1099
+ /* ── Preferences, Installation settings and About ─────────────────────────────────────────────────
1100
+ The three pages the avatar menu leads to. Each is a stack of raised cards or rows on the page ground,
1101
+ drawn from these few rules so the three read as one family. */
1102
+
1103
+ .pref-cards {
1104
+ display: flex;
1105
+ flex-direction: column;
1106
+ gap: 14px;
1107
+ }
1108
+
1109
+ .pref-card,
1110
+ .about-card {
1111
+ padding: 18px 20px;
1112
+ border-radius: 12px;
1113
+ border: 1px solid var(--border-hairline);
1114
+ background: var(--surface-raised);
1115
+ }
1116
+
1117
+ .pref-title {
1118
+ font-size: 15px;
1119
+ font-weight: 700;
1120
+ color: var(--text-strong);
1121
+ margin: 0 0 14px;
1122
+ }
1123
+
1124
+ .pref-dl {
1125
+ margin: 0;
1126
+ display: grid;
1127
+ grid-template-columns: auto 1fr;
1128
+ gap: 8px 18px;
1129
+ font-size: 14px;
1130
+ }
1131
+
1132
+ .pref-dl dt {
1133
+ color: var(--text-muted);
1134
+ }
1135
+
1136
+ .pref-dl dd {
1137
+ margin: 0;
1138
+ min-width: 0;
1139
+ color: var(--text-strong);
1140
+ }
1141
+
1142
+ .pref-address {
1143
+ word-break: break-all;
1144
+ }
1145
+
1146
+ .pref-none {
1147
+ color: var(--text-muted);
1148
+ }
1149
+
1150
+ .pref-line {
1151
+ margin: 14px 0 0;
1152
+ font-size: 13px;
1153
+ color: var(--text-muted);
1154
+ }
1155
+
1156
+ /* A link inside a sentence: accent ink and a quiet rule under it, so it reads as a link without shouting. */
1157
+ .pref-link,
1158
+ .about-link {
1159
+ color: var(--text-accent);
1160
+ text-decoration: none;
1161
+ border-bottom: 1px solid var(--accent-quiet);
1162
+ }
1163
+
1164
+ .pref-foot {
1165
+ margin-top: 12px;
1166
+ }
1167
+
1168
+ /* A pill that is the card's own action is a size up from a pill that labels a row. */
1169
+ .pref-foot .pill,
1170
+ .about-docs .pill {
1171
+ padding: 5px 13px;
1172
+ font-size: 12px;
1173
+ text-decoration: none;
1174
+ }
1175
+
1176
+ .pref-themes {
1177
+ display: flex;
1178
+ gap: 10px;
1179
+ flex-wrap: wrap;
1180
+ }
1181
+
1182
+ .pref-blur {
1183
+ display: flex;
1184
+ align-items: center;
1185
+ gap: 12px;
1186
+ }
1187
+
1188
+ /* THE TOP BAR'S EYE, GIVEN AN EDGE. On the top bar it sits in a row of buttons like it; alone in a card, a
1189
+ borderless glyph beside a sentence reads as a bullet. So here it carries the hairline edge the design
1190
+ draws round this control, and keeps the top bar's pressed state. */
1191
+ .pref-blur .icon-btn {
1192
+ flex: none;
1193
+ border-color: var(--border-hairline);
1194
+ }
1195
+
1196
+ .pref-blur-line {
1197
+ font-size: 13.5px;
1198
+ color: var(--text-muted);
1199
+ }
1200
+
1201
+ /* About: the source offer as one list of facts, the label column fixed so the values line up. */
1202
+ .about-dl {
1203
+ margin: 0;
1204
+ display: grid;
1205
+ grid-template-columns: 132px 1fr;
1206
+ gap: 10px 18px;
1207
+ align-items: baseline;
1208
+ font-size: 14px;
1209
+ }
1210
+
1211
+ .about-dl dt {
1212
+ color: var(--text-muted);
1213
+ }
1214
+
1215
+ .about-dl dd {
1216
+ margin: 0;
1217
+ min-width: 0;
1218
+ color: var(--text-strong);
1219
+ overflow-wrap: anywhere;
1220
+ }
1221
+
1222
+ .about-row {
1223
+ display: flex;
1224
+ align-items: center;
1225
+ gap: 10px;
1226
+ flex-wrap: wrap;
1227
+ }
1228
+
1229
+ .about-row .pill {
1230
+ text-decoration: none;
1231
+ }
1232
+
1233
+ .about-docs {
1234
+ display: flex;
1235
+ gap: 7px;
1236
+ flex-wrap: wrap;
1237
+ margin-top: 18px;
1238
+ padding-top: 14px;
1239
+ border-top: 1px solid var(--border-hairline);
1240
+ }
1241
+
1242
+ /* Installation settings: a section label, then rows. A provider category is a sub-heading with its sources
1243
+ beneath it, so the category is said once and never repeated on a row. The state column has a floor so
1244
+ every row's state lines up, whatever the name and coverage beside it. */
1245
+ .cfg-group {
1246
+ margin-top: 24px;
1247
+ }
1248
+
1249
+ .page-header + .cfg-group {
1250
+ margin-top: 4px;
1251
+ }
1252
+
1253
+ .cfg-rows {
1254
+ display: grid;
1255
+ gap: 6px;
1256
+ margin-top: 8px;
1257
+ }
1258
+
1259
+ .cfg-cat {
1260
+ margin-top: 16px;
1261
+ }
1262
+
1263
+ .cfg-sub {
1264
+ font-size: 13px;
1265
+ font-weight: 700;
1266
+ color: var(--text-muted);
1267
+ }
1268
+
1269
+ .cfg-cat-note {
1270
+ max-width: var(--prose-measure);
1271
+ margin: 3px 0 0;
1272
+ font-size: 12.5px;
1273
+ color: var(--text-muted);
1274
+ }
1275
+
1276
+ .cfg-row {
1277
+ padding: 10px 13px;
1278
+ border-radius: 9px;
1279
+ border: 1px solid var(--border-hairline);
1280
+ background: var(--surface-raised);
1281
+ }
1282
+
1283
+ .cfg-line {
1284
+ display: flex;
1285
+ align-items: center;
1286
+ gap: 12px;
1287
+ }
1288
+
1289
+ .cfg-name {
1290
+ flex: 1;
1291
+ min-width: 0;
1292
+ font-size: 13px;
1293
+ color: var(--text-strong);
1294
+ }
1295
+
1296
+ .cfg-val {
1297
+ font-size: 12.5px;
1298
+ color: var(--text-muted);
1299
+ text-align: right;
1300
+ }
1301
+
1302
+ .cfg-state {
1303
+ min-width: 92px;
1304
+ font-size: 12.5px;
1305
+ color: var(--text-muted);
1306
+ text-align: right;
1307
+ }
1308
+
1309
+ .cfg-state.bad,
1310
+ .cfg-note.bad {
1311
+ color: var(--tone-high);
1312
+ }
1313
+
1314
+ .cfg-state.off {
1315
+ color: var(--text-faint);
1316
+ }
1317
+
1318
+ /* Indented under the name, past the dot, so a row's second line reads as the row's own. */
1319
+ .cfg-note {
1320
+ margin: 6px 0 0 21px;
1321
+ font-size: 12.5px;
1322
+ color: var(--text-muted);
1323
+ overflow-wrap: anywhere;
1324
+ }
1325
+
1326
+ .cfg-act {
1327
+ display: flex;
1328
+ align-items: center;
1329
+ gap: 10px;
1330
+ flex-wrap: wrap;
1331
+ margin: 6px 0 0 21px;
1332
+ }
1333
+
1334
+ .cfg-act .cfg-note {
1335
+ margin: 0;
1336
+ }
1337
+
1338
+ .cfg-act .pill {
1339
+ text-decoration: none;
1340
+ cursor: pointer;
1341
+ }
1342
+
1343
+ .cfg-act .pill:disabled {
1344
+ cursor: default;
1345
+ }
1346
+
1347
+ .cfg-dot {
1348
+ flex: none;
1349
+ width: 9px;
1350
+ height: 9px;
1351
+ border-radius: 50%;
1352
+ background: var(--tone-minimal);
1353
+ }
1354
+
1355
+ .cfg-dot.bad {
1356
+ background: var(--tone-high);
1357
+ }
1358
+
1359
+ .cfg-dot.off {
1360
+ background: var(--text-faint);
1361
+ }
1362
+
865
1363
  /* ── mobile ──────────────────────────────────────────────────────────────────────────────────────── */
866
1364
 
867
1365
  @media (max-width: 899px) {
@@ -948,15 +1446,6 @@ input.filter {
948
1446
  margin: 6px 0 4px;
949
1447
  }
950
1448
 
951
- .fw-bmh {
952
- font-size: 10.5px;
953
- letter-spacing: 0.12em;
954
- text-transform: uppercase;
955
- color: var(--text-accent);
956
- font-weight: 700;
957
- margin: 16px 0 6px;
958
- }
959
-
960
1449
  /* THE rule that makes this read as a rated ladder instead of a bullet list: a fixed pill column and a
961
1450
  text column, so every meaning starts at the same left edge no matter how wide its band label is.
962
1451
  A flex row here lets each pill size itself and leaves the prose ragged — that is what the first
@@ -1000,69 +1489,306 @@ input.filter {
1000
1489
  .fw-bmrung {
1001
1490
  display: contents;
1002
1491
  }
1003
- .fw-bmrunglbl {
1004
- color: var(--text-body);
1005
- font-weight: 600;
1492
+ .fw-bmrunglbl {
1493
+ color: var(--text-body);
1494
+ font-weight: 600;
1495
+ }
1496
+ @media (max-width: 640px) {
1497
+ /* Stacked: one column, and the display:contents rows flow label-above-text in source order. */
1498
+ .fw-bmrungs {
1499
+ grid-template-columns: 1fr;
1500
+ row-gap: 1px;
1501
+ }
1502
+ }
1503
+
1504
+ .footer-demo-note {
1505
+ max-width: 420px; /* the note's width is the note's own, not its call site's */
1506
+ }
1507
+
1508
+ /* The line beside the primary action: what happens next when it is available, and why it is not when
1509
+ it is not. Capped here rather than at the call site for the same reason the note above is — width is
1510
+ a decision this file makes. Narrower than the note because it sits IN the action row, beside a
1511
+ button, rather than replacing it. */
1512
+ .footer-hint {
1513
+ max-width: 260px;
1514
+ font-size: 12px;
1515
+ line-height: 1.4;
1516
+ color: var(--text-muted);
1517
+ }
1518
+
1519
+ /* The acknowledgement a save leaves beside the button that was pressed.
1520
+ Quiet on purpose: it is a receipt, not an alert, and it sits in the same row as the primary action
1521
+ without competing with it. No new colour — `--tone-minimal` (the palette's clear-green) and the hairline are what the rest of
1522
+ this file already uses for a settled, good state. */
1523
+ .save-done {
1524
+ display: inline-flex;
1525
+ align-items: center;
1526
+ gap: 7px;
1527
+ font-size: 12.5px;
1528
+ color: var(--text-body);
1529
+ background: var(--surface-sunken);
1530
+ border: 1px solid var(--border-hairline);
1531
+ border-radius: 7px;
1532
+ padding: 7px 11px;
1533
+ }
1534
+ .save-done svg {
1535
+ color: var(--tone-minimal);
1536
+ flex: none;
1537
+ }
1538
+
1539
+ .fw-meta {
1540
+ font-size: 12px;
1541
+ color: var(--text-muted);
1542
+ margin-top: 4px;
1543
+ }
1544
+ .fw-meta b {
1545
+ color: var(--text-strong);
1546
+ }
1547
+
1548
+ /* Narrow viewports: the fixed pill column would squeeze the prose to a sliver, so the row stacks. */
1549
+ @media (max-width: 560px) {
1550
+ .fw-bmrow {
1551
+ grid-template-columns: 1fr;
1552
+ gap: 2px;
1553
+ padding: 6px 0;
1554
+ }
1555
+ }
1556
+
1557
+ /* ── Company settings: Profile, Projects, Search templates, New company ─────────────────────────────
1558
+ Each page is a stack of cards. The framework card leads Profile; Identity, Search defaults, the law
1559
+ firm fold and the background follow it, and New company draws the same cards in the same order. */
1560
+
1561
+ .card-stack {
1562
+ display: flex;
1563
+ flex-direction: column;
1564
+ gap: 14px;
1565
+ }
1566
+ .card-stack > .notice {
1567
+ margin: 0;
1568
+ }
1569
+ /* The fieldset that decides whether Profile can be changed is a stack in its own right, and a
1570
+ fieldset's own border, padding and minimum width are browser furniture nobody asked for. */
1571
+ fieldset.profile-cards {
1572
+ border: 0;
1573
+ padding: 0;
1574
+ margin: 0;
1575
+ min-width: 0;
1576
+ }
1577
+
1578
+ /* THE FOLD. A closed row whose chevron sits at its right edge and turns down when open — the shape every
1579
+ openable row on these pages takes, centre-aligned so the title, its note and the chevron share a line. */
1580
+ .fold-summary {
1581
+ display: flex;
1582
+ align-items: center;
1583
+ gap: 10px;
1584
+ list-style: none;
1585
+ cursor: pointer;
1586
+ font-weight: 700;
1587
+ font-size: 13.5px;
1588
+ color: var(--text-strong);
1589
+ }
1590
+ .fold-summary::-webkit-details-marker {
1591
+ display: none;
1592
+ }
1593
+ .fold-note {
1594
+ font-weight: 400;
1595
+ font-size: 12.5px;
1596
+ color: var(--text-muted);
1597
+ }
1598
+ .fold-chev {
1599
+ margin-left: auto;
1600
+ color: var(--text-faint);
1601
+ transition: transform 0.15s var(--ease-out);
1602
+ }
1603
+ details[open] > .fold-summary .fold-chev {
1604
+ transform: rotate(90deg);
1605
+ }
1606
+ @media (prefers-reduced-motion: reduce) {
1607
+ .fold-chev {
1608
+ transition: none;
1609
+ }
1610
+ }
1611
+ .fold-body {
1612
+ margin-top: 10px;
1613
+ }
1614
+ /* A fold inside a card: a hairline above it, so it reads as a row of the card rather than as a field. */
1615
+ .fw-fold {
1616
+ margin-top: 16px;
1617
+ border-top: 1px solid var(--border-hairline);
1618
+ padding-top: 12px;
1619
+ }
1620
+ /* A fold that IS a card: the whole card is the row until it opens. */
1621
+ .fold-card > .fold-summary {
1622
+ font-size: 14px;
1623
+ }
1624
+
1625
+ /* The configuration rows under the framework, in view whether the band detail is open or not. */
1626
+ .fw-rows {
1627
+ margin-top: 16px;
1628
+ border-top: 1px solid var(--border-hairline);
1629
+ padding-top: 12px;
1630
+ }
1631
+ .fw-row {
1632
+ display: flex;
1633
+ gap: 14px;
1634
+ padding: 5px 0;
1635
+ font-size: 13px;
1636
+ align-items: baseline;
1637
+ }
1638
+ .fw-row-label {
1639
+ color: var(--text-muted);
1640
+ min-width: 150px;
1641
+ }
1642
+ .fw-row-value {
1643
+ color: var(--text-strong);
1644
+ word-break: break-word;
1645
+ }
1646
+ .fw-foot {
1647
+ margin-top: 14px;
1648
+ }
1649
+
1650
+ /* One profile field: its label, its tag, its hint line, then the control. */
1651
+ .profile-field {
1652
+ display: block;
1653
+ margin-top: 18px;
1654
+ }
1655
+ .eyebrow + .profile-field,
1656
+ .eyebrow + div > .profile-field:first-child {
1657
+ margin-top: 12px;
1658
+ }
1659
+ .profile-field-label {
1660
+ display: block;
1661
+ font-weight: 700;
1662
+ color: var(--text-strong);
1663
+ font-size: 14px;
1664
+ }
1665
+ .profile-field-hint {
1666
+ display: block;
1667
+ font-size: 12.5px;
1668
+ color: var(--text-muted);
1669
+ margin: 2px 0 7px;
1670
+ }
1671
+ .profile-field-gap {
1672
+ display: block;
1673
+ height: 7px;
1674
+ }
1675
+
1676
+ /* REQUIRED AND OPTIONAL. Filled for the one a form cannot do without, quiet for the rest, so the eye finds
1677
+ the required field without reading every tag. Sized against the label it follows, and lowered a pixel
1678
+ so an uppercase tag sits on the label's line rather than above it. */
1679
+ .field-tag {
1680
+ display: inline-block;
1681
+ margin-left: 8px;
1682
+ padding: 1px 6px;
1683
+ border-radius: 5px;
1684
+ font-size: 10px;
1685
+ font-weight: 700;
1686
+ letter-spacing: 0.08em;
1687
+ line-height: 1.5;
1688
+ text-transform: uppercase;
1689
+ vertical-align: 1px;
1690
+ color: var(--text-faint);
1691
+ background: var(--surface-sunken);
1692
+ }
1693
+ .field-tag-required {
1694
+ color: var(--text-on-accent);
1695
+ background: var(--accent);
1696
+ }
1697
+
1698
+ /* The class search: the chosen classes as chips, then the box that finds one. */
1699
+ .chip-row {
1700
+ display: flex;
1701
+ align-items: center;
1702
+ gap: 6px;
1703
+ flex-wrap: wrap;
1704
+ }
1705
+ .class-picker > .chip-row {
1706
+ margin-top: 2px;
1006
1707
  }
1007
- @media (max-width: 640px) {
1008
- /* Stacked: one column, and the display:contents rows flow label-above-text in source order. */
1009
- .fw-bmrungs {
1010
- grid-template-columns: 1fr;
1011
- row-gap: 1px;
1012
- }
1708
+ .class-picker-box {
1709
+ position: relative;
1013
1710
  }
1014
1711
 
1015
- .footer-demo-note {
1016
- max-width: 420px; /* the note's width is the note's own, not its call site's */
1712
+ /* The New company rating card's one row: the framework a company starts on, and the way to its own. */
1713
+ .rating-row {
1714
+ display: flex;
1715
+ align-items: center;
1716
+ justify-content: space-between;
1717
+ gap: 12px;
1718
+ margin-top: 12px;
1719
+ }
1720
+ .rating-row-name {
1721
+ font-size: 14px;
1722
+ font-weight: 700;
1723
+ color: var(--text-strong);
1017
1724
  }
1018
1725
 
1019
- /* The line beside the primary action: what happens next when it is available, and why it is not when
1020
- it is not. Capped here rather than at the call site for the same reason the note above is — width is
1021
- a decision this file makes. Narrower than the note because it sits IN the action row, beside a
1022
- button, rather than replacing it. */
1023
- .footer-hint {
1024
- max-width: 260px;
1025
- font-size: 12px;
1026
- line-height: 1.4;
1726
+ /* The page's one action row: the button, and the line that says what state it is in. */
1727
+ .row-foot {
1728
+ display: flex;
1729
+ align-items: center;
1730
+ gap: 12px;
1731
+ flex-wrap: wrap;
1732
+ margin-top: 18px;
1733
+ }
1734
+ .row-foot-note {
1735
+ font-size: 13px;
1027
1736
  color: var(--text-muted);
1028
1737
  }
1029
1738
 
1030
- /* The acknowledgement a save leaves beside the button that was pressed.
1031
- Quiet on purpose: it is a receipt, not an alert, and it sits in the same row as the primary action
1032
- without competing with it. No new colour — `--tone-minimal` (the palette's clear-green) and the hairline are what the rest of
1033
- this file already uses for a settled, good state. */
1034
- .save-done {
1035
- display: inline-flex;
1036
- align-items: center;
1037
- gap: 7px;
1038
- font-size: 12.5px;
1039
- color: var(--text-body);
1040
- background: var(--surface-sunken);
1739
+ /* A project row: the whole left side opens it; its menu and chevron sit at the right. */
1740
+ .row-card {
1741
+ padding: 12px 14px;
1742
+ border-radius: 10px;
1041
1743
  border: 1px solid var(--border-hairline);
1042
- border-radius: 7px;
1043
- padding: 7px 11px;
1744
+ background: var(--surface-raised);
1745
+ display: flex;
1746
+ align-items: center;
1747
+ gap: 12px;
1044
1748
  }
1045
- .save-done svg {
1046
- color: var(--tone-minimal);
1749
+ .row-card-open {
1750
+ flex: 1;
1751
+ min-width: 0;
1752
+ text-align: left;
1753
+ background: none;
1754
+ border: 0;
1755
+ padding: 0;
1756
+ cursor: pointer;
1757
+ font: inherit;
1758
+ color: inherit;
1759
+ }
1760
+ .row-card-name {
1761
+ font-weight: 700;
1762
+ color: var(--text-strong);
1763
+ }
1764
+ .row-chev {
1765
+ display: inline-flex;
1047
1766
  flex: none;
1767
+ color: var(--text-faint);
1048
1768
  }
1049
1769
 
1050
- .fw-meta {
1051
- font-size: 12px;
1052
- color: var(--text-muted);
1053
- margin-top: 4px;
1770
+ /* A row's "More actions" menu, under its button and aligned to its right edge. */
1771
+ .actions-menu {
1772
+ position: relative;
1773
+ flex: none;
1054
1774
  }
1055
- .fw-meta b {
1056
- color: var(--text-strong);
1775
+ .actions-menu-button {
1776
+ width: 28px;
1777
+ height: 28px;
1057
1778
  }
1058
-
1059
- /* Narrow viewports: the fixed pill column would squeeze the prose to a sliver, so the row stacks. */
1060
- @media (max-width: 560px) {
1061
- .fw-bmrow {
1062
- grid-template-columns: 1fr;
1063
- gap: 2px;
1064
- padding: 6px 0;
1065
- }
1779
+ .actions-menu-list {
1780
+ position: absolute;
1781
+ right: 0;
1782
+ top: 32px;
1783
+ z-index: 30;
1784
+ min-width: 150px;
1785
+ padding: 6px;
1786
+ }
1787
+ .actions-menu-list .nav-item {
1788
+ font-size: 13px;
1789
+ padding: 7px 10px;
1790
+ margin: 0;
1791
+ white-space: nowrap;
1066
1792
  }
1067
1793
 
1068
1794
  /* ── the New clearance composer ──────────────────────────────────────────────────────────────────────
@@ -1227,20 +1953,7 @@ input.filter {
1227
1953
  text-decoration: underline;
1228
1954
  }
1229
1955
 
1230
- /* The two ways in. */
1231
- .entry-grid {
1232
- display: grid;
1233
- grid-template-columns: 1fr 1fr;
1234
- gap: 12px;
1235
- margin-top: 24px;
1236
- }
1237
-
1238
- @media (max-width: 640px) {
1239
- .entry-grid {
1240
- grid-template-columns: 1fr;
1241
- }
1242
- }
1243
-
1956
+ /* A card to choose by: the rows of the company picker. */
1244
1957
  .entry-card {
1245
1958
  text-align: left;
1246
1959
  padding: 18px;
@@ -1310,35 +2023,8 @@ input.filter {
1310
2023
  color: var(--text-muted);
1311
2024
  }
1312
2025
 
1313
- .segmented-entry {
1314
- display: inline-flex;
1315
- gap: 2px;
1316
- padding: 3px;
1317
- background: var(--surface-sunken);
1318
- border-radius: 9px;
1319
- border: 1px solid var(--border-hairline);
1320
- margin-top: 18px;
1321
- }
1322
-
1323
- .segmented-entry button {
1324
- padding: 6px 14px;
1325
- border: none;
1326
- border-radius: 6px;
1327
- font-family: var(--font-body);
1328
- font-size: 12.5px;
1329
- font-weight: 600;
1330
- cursor: pointer;
1331
- background: transparent;
1332
- color: var(--text-muted);
1333
- }
1334
-
1335
- .segmented-entry button[aria-pressed='true'] {
1336
- background: var(--surface-raised);
1337
- color: var(--text-strong);
1338
- }
1339
-
1340
- /* The column the wizard's steps live in. 720px, the design's measure for a form — NOT the footer's,
1341
- which spans the screen. */
2026
+ /* The column the form lives in. 720px, the design's measure for a form — NOT the footer's, which spans
2027
+ the screen. */
1342
2028
  /* ── §B — THE MEASURE MOVES OFF THE COLUMN AND ONTO ITS BLOCKS ────────────────────────────────────
1343
2029
  The owner: the New clearance page "is bound to an artificially narrow width; the table side-scrolls
1344
2030
  or wraps and looks worse than its content deserves. The top panel already fits the screen width."
@@ -1367,26 +2053,10 @@ input.filter {
1367
2053
  max-width: none;
1368
2054
  }
1369
2055
 
1370
- /* ── §B — "what this search includes", in or out at a glance ─────────────────────────────────────────
1371
- The list read as three sentences of equal weight, and whether an axis ran at all was buried in the
1372
- clause after the dash. The tick and the cross carry the answer; the colour is a reinforcement, never
1373
- the only signal, because a marker distinguishable by hue alone is not a marker for every reader. */
1374
- .carries {
1375
- margin: 8px 0 0;
1376
- padding: 0;
1377
- list-style: none;
1378
- }
1379
-
1380
- .carries li {
1381
- display: flex;
1382
- gap: 9px;
1383
- align-items: baseline;
1384
- font-size: 12.5px;
1385
- line-height: 1.55;
1386
- color: var(--text-muted);
1387
- padding: 2px 0;
1388
- }
1389
-
2056
+ /* ── §B — what a search carries, in or out at a glance ───────────────────────────────────────────────
2057
+ A list of three sentences of equal weight buried whether an axis ran at all in the clause after the
2058
+ dash. The tick and the cross carry the answer, on every search's row; the colour is a reinforcement,
2059
+ never the only signal, because a marker distinguishable by hue alone is not a marker for every reader. */
1390
2060
  .carries-mark {
1391
2061
  flex: none;
1392
2062
  width: 15px;
@@ -1544,50 +2214,6 @@ input.filter {
1544
2214
  background: var(--accent);
1545
2215
  }
1546
2216
 
1547
- /* The saved-search book SCROLLS rather than growing the page. A customer with forty of them should not
1548
- push the levers below the fold. */
1549
- .saved-list {
1550
- display: flex;
1551
- flex-direction: column;
1552
- gap: 7px;
1553
- max-height: 224px;
1554
- overflow-y: auto;
1555
- }
1556
-
1557
- .saved-row {
1558
- display: flex;
1559
- align-items: center;
1560
- gap: 11px;
1561
- padding: 11px 14px;
1562
- border-radius: 9px;
1563
- border: 1px solid var(--border-hairline);
1564
- background: var(--surface-raised);
1565
- cursor: pointer;
1566
- }
1567
-
1568
- .saved-row:hover {
1569
- border-color: var(--accent-ink);
1570
- }
1571
-
1572
- .saved-row-on {
1573
- border-color: var(--accent);
1574
- background: var(--accent-wash);
1575
- }
1576
-
1577
- .saved-retire {
1578
- flex: none;
1579
- display: inline-flex;
1580
- background: none;
1581
- border: none;
1582
- color: var(--text-faint);
1583
- cursor: pointer;
1584
- padding: 4px;
1585
- }
1586
-
1587
- .saved-retire:hover {
1588
- color: var(--text-accent);
1589
- }
1590
-
1591
2217
  .lever {
1592
2218
  display: flex;
1593
2219
  align-items: center;
@@ -1832,6 +2458,13 @@ input.filter {
1832
2458
  border-color: var(--text-strong);
1833
2459
  }
1834
2460
 
2461
+ /* The page header's buttons: the same two kinds, sized to sit beside a title. */
2462
+ .btn-sm {
2463
+ padding: 8px 14px;
2464
+ font-size: 13px;
2465
+ border-radius: 8px;
2466
+ }
2467
+
1835
2468
  /* ── the confirm dialog ─────────────────────────────────────────────────────────────────────────────
1836
2469
  The mandatory step. Nothing is spent until Start is pressed, and Back returns to a fully preserved
1837
2470
  form — so the scrim closes it rather than trapping anyone. */
@@ -1877,7 +2510,10 @@ input.filter {
1877
2510
  }
1878
2511
 
1879
2512
  .stop-choice-opt {
1880
- display: block;
2513
+ display: grid;
2514
+ grid-template-columns: auto 1fr;
2515
+ column-gap: 10px;
2516
+ align-items: baseline;
1881
2517
  width: 100%;
1882
2518
  text-align: left;
1883
2519
  padding: 13px 15px;
@@ -1892,7 +2528,15 @@ input.filter {
1892
2528
  border-color: var(--accent-ink);
1893
2529
  }
1894
2530
 
2531
+ /* The radio is the row's own control, drawn in the accent so the selected option reads at a glance. */
2532
+ .stop-choice-opt input {
2533
+ grid-row: 1 / span 2;
2534
+ margin: 2px 0 0;
2535
+ accent-color: var(--accent);
2536
+ }
2537
+
1895
2538
  .stop-choice-opt b {
2539
+ grid-column: 2;
1896
2540
  display: block;
1897
2541
  font-size: 14px;
1898
2542
  color: var(--text-strong);
@@ -1900,6 +2544,7 @@ input.filter {
1900
2544
  }
1901
2545
 
1902
2546
  .stop-choice-opt span {
2547
+ grid-column: 2;
1903
2548
  display: block;
1904
2549
  font-size: 12.5px;
1905
2550
  line-height: 1.5;
@@ -1910,6 +2555,12 @@ input.filter {
1910
2555
  border-color: var(--tone-medium);
1911
2556
  }
1912
2557
 
2558
+ /* THE SELECTED OPTION IS THE ONE THE BUTTON NAMES, so it carries the same accent the button does. */
2559
+ .stop-choice-opt.selected {
2560
+ border-color: var(--accent);
2561
+ background: var(--accent-wash);
2562
+ }
2563
+
1913
2564
  .modal-rule {
1914
2565
  position: absolute;
1915
2566
  left: 0;
@@ -1968,6 +2619,78 @@ input.filter {
1968
2619
  .home2-band-count { font-size: 11.5px; color: var(--text-faint); white-space: nowrap; }
1969
2620
  .home2-band-rule { height: 1px; background: var(--border-hairline); }
1970
2621
 
2622
+ /* THE ALLOWANCE LINE. Quiet by construction — no band, no heading, no colour — because it states a fact
2623
+ about the account rather than a fault with the form. It appears only at five searches or fewer. */
2624
+ .nc-allowance { margin: -6px 0 18px; font-size: 13px; color: var(--text-muted); }
2625
+
2626
+ /* ── New clearance: one form, top to bottom ──────────────────────────────────────────────────────────
2627
+ Quiet additions to the kit the form already used. The recommendation tag is the one accent on a row
2628
+ that is not selected, because it is the one thing on it the reader did not choose. */
2629
+ .nc-company { max-width: 340px; }
2630
+ .nc-company-name { font-size: 15px; font-weight: 600; color: var(--text-strong); }
2631
+ .nc-project { max-width: 340px; flex: 1; }
2632
+ .nc-describe-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
2633
+ .nc-optional { font-size: 11.5px; color: var(--text-faint); }
2634
+ .nc-which-head {
2635
+ display: flex;
2636
+ align-items: flex-end;
2637
+ justify-content: space-between;
2638
+ gap: 12px;
2639
+ flex-wrap: wrap;
2640
+ margin-bottom: 10px;
2641
+ }
2642
+ .nc-templates { display: inline-flex; align-items: flex-end; gap: 8px; }
2643
+ .nc-templates label { display: block; }
2644
+ .nc-templates select { min-width: 190px; }
2645
+ .nc-template-line { margin: 0 0 10px; font-size: 12.5px; line-height: 1.6; color: var(--text-muted); }
2646
+
2647
+ /* A small action beside the thing it acts on — Manage, Manage projects, Clear template, Show all. */
2648
+ .chip-btn {
2649
+ display: inline-flex;
2650
+ align-items: center;
2651
+ padding: 3px 10px;
2652
+ border-radius: 999px;
2653
+ border: 1px solid var(--border-hairline);
2654
+ background: var(--surface-sunken);
2655
+ color: var(--text-muted);
2656
+ font-family: var(--font-body);
2657
+ font-size: 11px;
2658
+ font-weight: 700;
2659
+ white-space: nowrap;
2660
+ cursor: pointer;
2661
+ }
2662
+ .chip-btn:hover { border-color: var(--accent-ink); color: var(--text-accent); }
2663
+
2664
+ /* The search row as a card holding its pick button and, when selected, its own option. */
2665
+ .pick-row-stack { flex-direction: column; align-items: stretch; gap: 10px; }
2666
+ .pick-row-hit {
2667
+ display: flex;
2668
+ align-items: flex-start;
2669
+ gap: 12px;
2670
+ width: 100%;
2671
+ padding: 0;
2672
+ border: 0;
2673
+ background: none;
2674
+ font: inherit;
2675
+ color: inherit;
2676
+ text-align: left;
2677
+ cursor: pointer;
2678
+ }
2679
+ .pick-row-hit:disabled { cursor: default; }
2680
+ .pick-row-inside { margin-left: 27px; }
2681
+ .pick-tag {
2682
+ padding: 1px 8px;
2683
+ border-radius: 999px;
2684
+ border: 1px solid var(--accent-ink);
2685
+ background: var(--accent-wash);
2686
+ color: var(--text-accent);
2687
+ font-size: 10.5px;
2688
+ font-weight: 700;
2689
+ }
2690
+ .pick-row-reason { display: block; margin-top: 3px; font-size: 12px; color: var(--text-body); }
2691
+ .pick-chips { display: flex; flex-wrap: wrap; gap: 3px 16px; margin-top: 6px; font-size: 12px; color: var(--text-muted); }
2692
+ .pick-chips > span { display: inline-flex; align-items: baseline; gap: 6px; }
2693
+
1971
2694
  /* A RUNNING RUN AS A ROW. The queue's grid, with the two columns a running row has and a waiting one
1972
2695
  does not. Declared as a modifier rather than a new grid so the narrow-screen rule that hides columns
1973
2696
  keeps one definition for both. */
@@ -2120,6 +2843,20 @@ input.filter {
2120
2843
  margin: 0 0 10px; font-size: 12.5px; font-weight: 600; letter-spacing: .02em; color: var(--text-muted);
2121
2844
  }
2122
2845
 
2846
+ /* THE FOLDS: the key door beside a sign-in door, and "Setup steps" once connected. Both open only when the
2847
+ reader opens them; the summary is the heading, with a chevron that turns when open. */
2848
+ .steps-alt-fold > summary,
2849
+ .steps-fold > summary { display: flex; align-items: center; gap: 10px; list-style: none; cursor: pointer; }
2850
+ .steps-alt-fold > summary::-webkit-details-marker,
2851
+ .steps-fold > summary::-webkit-details-marker { display: none; }
2852
+ .steps-alt-fold > summary .steps-alt-head,
2853
+ .steps-fold > summary .steps-alt-head { margin: 0; }
2854
+ .fold-chev { margin-left: auto; flex: none; color: var(--text-faint); transition: transform 0.15s var(--ease-out); }
2855
+ .steps-alt-fold[open] > summary .fold-chev,
2856
+ .steps-fold[open] > summary > .fold-chev { transform: rotate(90deg); }
2857
+ .steps-alt-fold > ol.steps { margin-top: 14px; }
2858
+ .steps-fold-body { margin-top: 14px; }
2859
+
2123
2860
  .home2-acked { margin-top: 13px; }
2124
2861
  .home2-acked-toggle {
2125
2862
  padding: 0; background: none; border: 0; cursor: pointer; font: inherit; font-size: 12.5px;
@@ -2304,7 +3041,24 @@ input.filter {
2304
3041
 
2305
3042
  .ai-connect { margin-top: 34px; }
2306
3043
 
2307
- .ai-screen .ai-connect-head { margin: 0 0 12px; }
3044
+ .ai-screen .ai-connect-head { margin: 0; }
3045
+
3046
+ /* "Connect it" and, beside it, whether this reader's assistant has been seen calling. */
3047
+ .ai-connect-top { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
3048
+ .conn-state {
3049
+ display: inline-flex; align-items: center; gap: 7px; padding: 3px 11px 3px 9px;
3050
+ border: 1px solid var(--border-hairline); border-radius: 999px; background: var(--surface-raised);
3051
+ font-size: 12.5px; color: var(--text-muted);
3052
+ }
3053
+ .conn-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
3054
+ .conn-dot-on { background: var(--tone-minimal); }
3055
+
3056
+ /* "Optional", quietly, at the end of the standfirst — never the word the page opens with. */
3057
+ .lede-opt {
3058
+ display: inline-block; margin-left: 6px; padding: 2px 7px; vertical-align: 2px; border-radius: 5px;
3059
+ background: var(--surface-sunken); font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
3060
+ text-transform: uppercase; color: var(--text-faint);
3061
+ }
2308
3062
 
2309
3063
  /* ── THE WHERE-CHOICE ─────────────────────────────────────────────────────────────────────────────
2310
3064
  ONE control, the same size before and after a pick. Selection is shown the way every other choice in
@@ -2370,6 +3124,13 @@ input.filter {
2370
3124
  card can show — so neither picking an app nor pressing Copy moves anything around it. */
2371
3125
  .ai-slot { position: relative; min-height: 180px; }
2372
3126
 
3127
+ /* A closed fold reserves nothing: the reserved height is for steps that are drawn. */
3128
+ .ai-slot.is-folded { min-height: 0; }
3129
+
3130
+ /* The way back to the report the reader came from, above the steps. */
3131
+ .back-to-report { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
3132
+ .back-to-report-line { font-size: 13.5px; color: var(--text-muted); }
3133
+
2373
3134
  /* The hidden copy of every panel the slot's height is taken from. Laid out at the slot's own width, never
2374
3135
  seen, never reached by a pointer or a keyboard. */
2375
3136
  .ai-probe {
@@ -2486,6 +3247,9 @@ ol.steps > li::before {
2486
3247
 
2487
3248
  .secret-btn { margin-top: 8px; }
2488
3249
 
3250
+ /* A folded list's buttons are quiet: the steps above the fold are the way in. */
3251
+ .ai-screen .btn-sm { padding: 6px 11px; font-size: 12.5px; }
3252
+
2489
3253
  /* THE PRESSED CONTROL IS THE CONFIRMATION: solid accent on the button pressed, never a muted caption. */
2490
3254
  .is-copied,
2491
3255
  .is-copied:hover,
@@ -2530,8 +3294,31 @@ ol.steps > li::before {
2530
3294
 
2531
3295
  .ai-help p { margin: 6px 0 0; color: var(--text-muted); font-size: 13.5px; line-height: 1.55; }
2532
3296
 
3297
+ .ai-help-guide { font-size: 12px; text-decoration: none; cursor: pointer; }
3298
+
3299
+
3300
+ /* ── People: what the Permissions column's words mean, under the column ─────────────────────────────────
3301
+ A two-column key, term beside meaning, so each word lines up with the sentence it stands for. */
3302
+ .perm-key {
3303
+ display: grid;
3304
+ grid-template-columns: auto 1fr;
3305
+ gap: 3px 14px;
3306
+ margin: 14px 0 0;
3307
+ font-size: 12.5px;
3308
+ color: var(--text-muted);
3309
+ }
3310
+
3311
+ .perm-key dt {
3312
+ color: var(--text-strong);
3313
+ font-weight: 600;
3314
+ white-space: nowrap;
3315
+ }
3316
+
3317
+ .perm-key dd {
3318
+ margin: 0;
3319
+ }
2533
3320
 
2534
- /* ── Give someone access: the points on the tree a new person can be given ─────────────────────────────
3321
+ /* ── Give access: the points on the tree a new person can be given ───────────────────────────────────────
2535
3322
  One row per point. A BUTTON, because choosing a point is an action and a keyboard has to reach it; the
2536
3323
  resets below are what make a button read as a row. `.sub` indents a company under the organisation
2537
3324
  it sits in. A row covered by a wider choice above it — everything, or its whole organisation — is