clearotron 0.3.2-beta.1 → 0.3.2-beta.3

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 (53) hide show
  1. package/.env.example +29 -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 +15 -13
  19. package/docs/architecture/05-config-governance.md +39 -30
  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 +33 -0
  30. package/driver/driver.config.mjs +19 -3
  31. package/driver/package.json +1 -1
  32. package/driver/portal-service.mjs +34 -4
  33. package/driver/suite-census.json +118 -46
  34. package/mcp-server/CHANGELOG.md +8 -0
  35. package/mcp-server/package.json +1 -1
  36. package/mcp-server/packs/client/CONNECT.md +6 -6
  37. package/package.json +1 -1
  38. package/portal-ui/dist/assets/{index-BsbasHjM.js → index-Bki5jT_N.js} +3376 -2971
  39. package/portal-ui/dist/assets/{index-DNQpLYZF.css → index-De2RFLbT.css} +972 -205
  40. package/portal-ui/dist/index.html +2 -2
  41. package/portal-ui/package.json +1 -1
  42. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  43. package/providers/oauth-mcp-bridge/package.json +1 -1
  44. package/providers/signa/src/core.js +29 -1
  45. package/scripts/ask-ai-render-check.mjs +287 -105
  46. package/scripts/release-note-required.mjs +16 -2
  47. package/scripts/release-pre-gate.mjs +111 -0
  48. package/scripts/settings-render-check.mjs +575 -0
  49. package/shared/brand.mjs +29 -0
  50. package/shared/connect-clients.mjs +99 -47
  51. package/shared/names-in-force.mjs +1 -0
  52. package/shared/stdio-connect.mjs +16 -2
  53. 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);
@@ -521,42 +568,19 @@ table.data.fixed td {
521
568
  /* An expanded read: quiet enough to read as subordinate to the row it belongs to. The indent is on the
522
569
  cells, not here, because it is structural — see ReadRow. */
523
570
  table.data tr.read-row > td {
524
- background: var(--surface-sunken);
571
+ background-color: var(--surface-sunken);
525
572
  }
526
573
 
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. */
574
+ /* A search with a report can still be opened with a click anywhere on its row — the mouse's convenience.
575
+ The named, focusable control is its Open button in the actions column; the row takes no focus of its
576
+ own, so a keyboard meets one target per search rather than two.
577
+ A search with no report gets none of this — not the pointer, not the hover. A dead target is worse
578
+ than no target. */
533
579
  table.data tr.read-row.openable > td {
534
580
  cursor: pointer;
535
581
  }
536
582
  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);
583
+ background-color: var(--surface-raised);
560
584
  }
561
585
 
562
586
  table.data tr.row {
@@ -606,8 +630,14 @@ button.twisty:focus-visible {
606
630
  white-space: nowrap;
607
631
  }
608
632
 
609
- table.data tr.row:hover td {
610
- background: var(--surface-sunken);
633
+ table.data tr.row:not(.inert):hover td {
634
+ background-color: var(--surface-sunken);
635
+ }
636
+
637
+ /* A name with one search and no report — running for the first time — has nothing to open and nothing to
638
+ expand, so its row does not pretend to be a control. */
639
+ table.data tr.row.inert {
640
+ cursor: default;
611
641
  }
612
642
 
613
643
  /* A SECTION HEADER, not a decorative divider.
@@ -617,7 +647,9 @@ table.data tr.row:hover td {
617
647
  Body-text size, medium weight, full-strength ink, sentence case rather than letterspaced caps — and a
618
648
  hairline rule spanning the table beneath it, so containment is visible without reading anything. */
619
649
  table.data tr.group-head td {
620
- padding: 22px 12px 7px;
650
+ padding: 10px 12px;
651
+ background: var(--surface-sunken);
652
+ border-top: 1px solid var(--border-strong);
621
653
  border-bottom: 1px solid var(--border-strong);
622
654
  }
623
655
 
@@ -635,10 +667,177 @@ table.data tr.group-head .owner-count {
635
667
  color: var(--text-muted);
636
668
  }
637
669
 
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;
670
+ /* NESTING IS DRAWN, not only indented. The company heading is a filled band; a name inside a group carries
671
+ one vertical rule in its first cell, and a search under a name one rule per level above it. Drawn as
672
+ background images so a rule costs no width and cannot move a column, and so it survives the row's own
673
+ background colour. */
674
+ table.data td.nest-1,
675
+ table.data td.nest-2 {
676
+ background-repeat: no-repeat;
677
+ }
678
+ table.data td.nest-1 {
679
+ background-image: linear-gradient(var(--border-strong), var(--border-strong));
680
+ background-size: 1px 100%;
681
+ background-position: 7px 0;
682
+ }
683
+ table.data td.nest-2 {
684
+ background-image: linear-gradient(var(--border-strong), var(--border-strong)), linear-gradient(var(--border-strong), var(--border-strong));
685
+ background-size: 1px 100%, 1px 100%;
686
+ background-position: 7px 0, 17px 0;
687
+ }
688
+
689
+ /* "Group ·" before a group's name: what kind of row this is, quieter than the name it introduces. */
690
+ .row-kind {
691
+ margin-right: 6px;
692
+ font-size: 13px;
693
+ color: var(--text-muted);
694
+ }
695
+
696
+ /* ── the Clearances actions column ──────────────────────────────────────────────────────────────────
697
+ Open, Ask AI and the row menu in fixed tracks, so every Open on the page shares one x and one width.
698
+ Narrow, Ask AI sits under Open; with room, beside it — the table's wrapper is the container measured,
699
+ so a collapsed rail widens it exactly as a wider window does. */
700
+ .names-wrap {
701
+ container-type: inline-size;
702
+ /* VISIBLE, not the wrapper's usual `auto`. This table cannot overflow — its columns are shares of its
703
+ own width — so the scroll container bought nothing here, and it clipped on both axes: an Ask AI
704
+ panel opened from a row near the bottom of the list was cut off at the table's edge. */
705
+ overflow: visible;
706
+ }
707
+ /* Eight pixels either side instead of twelve, on every column but the two narrow ones at the left: the
708
+ actions column takes a quarter of the table, and the text columns keep their words on one line by
709
+ giving up padding rather than width. The date and the actions give up a little more. */
710
+ .names-wrap table.data.fixed th:nth-child(n + 3),
711
+ .names-wrap table.data.fixed td:nth-child(n + 3) {
712
+ padding-left: 8px;
713
+ padding-right: 8px;
714
+ }
715
+ .names-wrap table.data.fixed td.col-date,
716
+ .names-wrap table.data.fixed td.col-actions {
717
+ padding-left: 6px;
718
+ padding-right: 6px;
719
+ }
720
+ /* A status that does not fit wraps between its halves — "Queued ·" over "1 search" — rather than running
721
+ under the Risk column: `.status` is nowrap everywhere else, which is right for a word and wrong for a
722
+ phrase in a column of fixed width. The dot stays level with the first line. */
723
+ .names-wrap table.data.fixed td .status {
724
+ white-space: normal;
725
+ align-items: flex-start;
726
+ }
727
+ .names-wrap table.data.fixed td .status .dot {
728
+ margin-top: 0.42em;
729
+ }
730
+ .status-count {
731
+ white-space: nowrap;
732
+ }
733
+ /* A date is one word to a reader. A line may break before it, never at its hyphens: "2026-09-" over "02"
734
+ read as a different date on the line below. */
735
+ .names-wrap table.data.fixed td .sub.report-line {
736
+ overflow-wrap: normal;
737
+ }
738
+ .names-wrap .nowrap-date {
739
+ white-space: nowrap;
740
+ }
741
+ .row-actions {
742
+ /* WIDE ENOUGH FOR THE LONGEST LABEL ON A FONT THIS BOX DOES NOT HAVE. The track is fixed so every
743
+ Open shares one width — "the track decides, not the text" — which means the number has to clear
744
+ the longest label, "Open latest report", everywhere. At 11em it cleared it on a developer machine
745
+ and clipped on the CI runner, whose default font is wider: the browser check went green locally
746
+ and red in CI on the same commit. 13.5em is the local measurement plus about a fifth, which is
747
+ more than the spread between the font stacks the two see. */
748
+ --row-open-w: 13.5em;
749
+ --row-ask-w: 6.5em;
750
+ display: grid;
751
+ grid-template-columns: var(--row-open-w) auto;
752
+ grid-template-areas: 'open more' 'ask more';
753
+ justify-content: start;
754
+ align-items: start;
755
+ gap: 6px;
756
+ font-size: 12.5px;
757
+ }
758
+ .row-actions-open { grid-area: open; }
759
+ /* Ask AI in a row: the header's button, compacted to the height of Open beside it and kept on one line —
760
+ in its own track it wrapped to "Ask" over "AI" with its icon stranded beside them. */
761
+ .row-actions .btn-ghost.ask-ai-btn {
762
+ padding: 5px 8px;
763
+ gap: 5px;
764
+ white-space: nowrap;
765
+ }
766
+ .row-actions-ask { grid-area: ask; }
767
+ .row-actions-more { grid-area: more; }
768
+ .row-actions-open:empty,
769
+ .row-actions-ask:empty {
770
+ display: none;
771
+ }
772
+ @container (min-width: 1000px) {
773
+ .row-actions {
774
+ grid-template-columns: var(--row-open-w) var(--row-ask-w) auto;
775
+ grid-template-areas: 'open ask more';
776
+ }
777
+ .row-actions-open:empty,
778
+ .row-actions-ask:empty {
779
+ display: block;
780
+ }
781
+ }
782
+ /* The bordered Open. One width, whatever its label says: the track decides, not the text. */
783
+ button.row-open {
784
+ width: 100%;
785
+ padding: 5px 8px;
786
+ border: 1px solid var(--border-strong);
787
+ border-radius: 7px;
788
+ background: var(--surface-raised);
789
+ color: var(--text-strong);
790
+ font: inherit;
791
+ font-weight: 600;
792
+ white-space: nowrap;
793
+ cursor: pointer;
794
+ }
795
+ button.row-open:hover {
796
+ border-color: var(--text-muted);
797
+ }
798
+ button.row-open:focus-visible,
799
+ button.row-menu-btn:focus-visible {
800
+ outline: 2px solid var(--accent);
801
+ outline-offset: 1px;
802
+ }
803
+ .row-menu {
804
+ position: relative;
805
+ }
806
+ button.row-menu-btn {
807
+ width: 28px;
808
+ height: 28px;
809
+ padding: 0;
810
+ border: 0;
811
+ border-radius: 6px;
812
+ background: none;
813
+ color: var(--text-muted);
814
+ font: inherit;
815
+ font-size: 15px;
816
+ line-height: 1;
817
+ cursor: pointer;
818
+ }
819
+ button.row-menu-btn:hover,
820
+ button.row-menu-btn[aria-expanded='true'] {
821
+ background: var(--surface-sunken);
822
+ color: var(--text-strong);
823
+ }
824
+ /* To the LEFT of the button, level with it. Every row menu holds one act, and a list of one fits beside
825
+ its button inside its own row — so opening it covers the row it acts on, never the rows beneath, and
826
+ it cannot be the thing that runs off the bottom of the page. */
827
+ .row-menu-list {
828
+ position: absolute;
829
+ right: 100%;
830
+ top: -4px;
831
+ margin-right: 4px;
832
+ min-width: 132px;
833
+ padding: 4px;
834
+ z-index: 50;
835
+ }
836
+ button.row-menu-item {
837
+ font-size: 13px;
838
+ padding: 6px 10px;
839
+ margin: 0;
840
+ white-space: nowrap;
642
841
  }
643
842
 
644
843
  .pager {
@@ -691,6 +890,21 @@ table.data tr.group-head ~ tr.row > td:first-child {
691
890
  color: var(--text-muted);
692
891
  }
693
892
 
893
+ /* The larger pill, for a control that is a card's closing action rather than a label — and an anchor
894
+ drawn as one, which is what opening a document in a new tab is. */
895
+ .pill-lg {
896
+ padding: 5px 13px;
897
+ font-size: 12px;
898
+ }
899
+ a.pill {
900
+ text-decoration: none;
901
+ cursor: pointer;
902
+ }
903
+ a.pill:hover {
904
+ color: var(--text-strong);
905
+ border-color: var(--border-strong);
906
+ }
907
+
694
908
  /* ── status + risk ───────────────────────────────────────────────────────────────────────────────── */
695
909
 
696
910
  .dot {
@@ -707,7 +921,7 @@ table.data tr.group-head ~ tr.row > td:first-child {
707
921
  }
708
922
 
709
923
  .dot.running {
710
- background: var(--accent);
924
+ background: var(--text-muted);
711
925
  animation: cordPulse 1.4s ease-in-out infinite;
712
926
  }
713
927
 
@@ -862,6 +1076,270 @@ input.filter {
862
1076
  background: var(--surface-raised);
863
1077
  }
864
1078
 
1079
+ /* ── Preferences, Installation settings and About ─────────────────────────────────────────────────
1080
+ The three pages the avatar menu leads to. Each is a stack of raised cards or rows on the page ground,
1081
+ drawn from these few rules so the three read as one family. */
1082
+
1083
+ .pref-cards {
1084
+ display: flex;
1085
+ flex-direction: column;
1086
+ gap: 14px;
1087
+ }
1088
+
1089
+ .pref-card,
1090
+ .about-card {
1091
+ padding: 18px 20px;
1092
+ border-radius: 12px;
1093
+ border: 1px solid var(--border-hairline);
1094
+ background: var(--surface-raised);
1095
+ }
1096
+
1097
+ .pref-title {
1098
+ font-size: 15px;
1099
+ font-weight: 700;
1100
+ color: var(--text-strong);
1101
+ margin: 0 0 14px;
1102
+ }
1103
+
1104
+ .pref-dl {
1105
+ margin: 0;
1106
+ display: grid;
1107
+ grid-template-columns: auto 1fr;
1108
+ gap: 8px 18px;
1109
+ font-size: 14px;
1110
+ }
1111
+
1112
+ .pref-dl dt {
1113
+ color: var(--text-muted);
1114
+ }
1115
+
1116
+ .pref-dl dd {
1117
+ margin: 0;
1118
+ min-width: 0;
1119
+ color: var(--text-strong);
1120
+ }
1121
+
1122
+ .pref-address {
1123
+ word-break: break-all;
1124
+ }
1125
+
1126
+ .pref-none {
1127
+ color: var(--text-muted);
1128
+ }
1129
+
1130
+ .pref-line {
1131
+ margin: 14px 0 0;
1132
+ font-size: 13px;
1133
+ color: var(--text-muted);
1134
+ }
1135
+
1136
+ /* A link inside a sentence: accent ink and a quiet rule under it, so it reads as a link without shouting. */
1137
+ .pref-link,
1138
+ .about-link {
1139
+ color: var(--text-accent);
1140
+ text-decoration: none;
1141
+ border-bottom: 1px solid var(--accent-quiet);
1142
+ }
1143
+
1144
+ .pref-foot {
1145
+ margin-top: 12px;
1146
+ }
1147
+
1148
+ /* A pill that is the card's own action is a size up from a pill that labels a row. */
1149
+ .pref-foot .pill,
1150
+ .about-docs .pill {
1151
+ padding: 5px 13px;
1152
+ font-size: 12px;
1153
+ text-decoration: none;
1154
+ }
1155
+
1156
+ .pref-themes {
1157
+ display: flex;
1158
+ gap: 10px;
1159
+ flex-wrap: wrap;
1160
+ }
1161
+
1162
+ .pref-blur {
1163
+ display: flex;
1164
+ align-items: center;
1165
+ gap: 12px;
1166
+ }
1167
+
1168
+ /* 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
1169
+ borderless glyph beside a sentence reads as a bullet. So here it carries the hairline edge the design
1170
+ draws round this control, and keeps the top bar's pressed state. */
1171
+ .pref-blur .icon-btn {
1172
+ flex: none;
1173
+ border-color: var(--border-hairline);
1174
+ }
1175
+
1176
+ .pref-blur-line {
1177
+ font-size: 13.5px;
1178
+ color: var(--text-muted);
1179
+ }
1180
+
1181
+ /* About: the source offer as one list of facts, the label column fixed so the values line up. */
1182
+ .about-dl {
1183
+ margin: 0;
1184
+ display: grid;
1185
+ grid-template-columns: 132px 1fr;
1186
+ gap: 10px 18px;
1187
+ align-items: baseline;
1188
+ font-size: 14px;
1189
+ }
1190
+
1191
+ .about-dl dt {
1192
+ color: var(--text-muted);
1193
+ }
1194
+
1195
+ .about-dl dd {
1196
+ margin: 0;
1197
+ min-width: 0;
1198
+ color: var(--text-strong);
1199
+ overflow-wrap: anywhere;
1200
+ }
1201
+
1202
+ .about-row {
1203
+ display: flex;
1204
+ align-items: center;
1205
+ gap: 10px;
1206
+ flex-wrap: wrap;
1207
+ }
1208
+
1209
+ .about-row .pill {
1210
+ text-decoration: none;
1211
+ }
1212
+
1213
+ .about-docs {
1214
+ display: flex;
1215
+ gap: 7px;
1216
+ flex-wrap: wrap;
1217
+ margin-top: 18px;
1218
+ padding-top: 14px;
1219
+ border-top: 1px solid var(--border-hairline);
1220
+ }
1221
+
1222
+ /* Installation settings: a section label, then rows. A provider category is a sub-heading with its sources
1223
+ beneath it, so the category is said once and never repeated on a row. The state column has a floor so
1224
+ every row's state lines up, whatever the name and coverage beside it. */
1225
+ .cfg-group {
1226
+ margin-top: 24px;
1227
+ }
1228
+
1229
+ .page-header + .cfg-group {
1230
+ margin-top: 4px;
1231
+ }
1232
+
1233
+ .cfg-rows {
1234
+ display: grid;
1235
+ gap: 6px;
1236
+ margin-top: 8px;
1237
+ }
1238
+
1239
+ .cfg-cat {
1240
+ margin-top: 16px;
1241
+ }
1242
+
1243
+ .cfg-sub {
1244
+ font-size: 13px;
1245
+ font-weight: 700;
1246
+ color: var(--text-muted);
1247
+ }
1248
+
1249
+ .cfg-cat-note {
1250
+ max-width: var(--prose-measure);
1251
+ margin: 3px 0 0;
1252
+ font-size: 12.5px;
1253
+ color: var(--text-muted);
1254
+ }
1255
+
1256
+ .cfg-row {
1257
+ padding: 10px 13px;
1258
+ border-radius: 9px;
1259
+ border: 1px solid var(--border-hairline);
1260
+ background: var(--surface-raised);
1261
+ }
1262
+
1263
+ .cfg-line {
1264
+ display: flex;
1265
+ align-items: center;
1266
+ gap: 12px;
1267
+ }
1268
+
1269
+ .cfg-name {
1270
+ flex: 1;
1271
+ min-width: 0;
1272
+ font-size: 13px;
1273
+ color: var(--text-strong);
1274
+ }
1275
+
1276
+ .cfg-val {
1277
+ font-size: 12.5px;
1278
+ color: var(--text-muted);
1279
+ text-align: right;
1280
+ }
1281
+
1282
+ .cfg-state {
1283
+ min-width: 92px;
1284
+ font-size: 12.5px;
1285
+ color: var(--text-muted);
1286
+ text-align: right;
1287
+ }
1288
+
1289
+ .cfg-state.bad,
1290
+ .cfg-note.bad {
1291
+ color: var(--tone-high);
1292
+ }
1293
+
1294
+ .cfg-state.off {
1295
+ color: var(--text-faint);
1296
+ }
1297
+
1298
+ /* Indented under the name, past the dot, so a row's second line reads as the row's own. */
1299
+ .cfg-note {
1300
+ margin: 6px 0 0 21px;
1301
+ font-size: 12.5px;
1302
+ color: var(--text-muted);
1303
+ overflow-wrap: anywhere;
1304
+ }
1305
+
1306
+ .cfg-act {
1307
+ display: flex;
1308
+ align-items: center;
1309
+ gap: 10px;
1310
+ flex-wrap: wrap;
1311
+ margin: 6px 0 0 21px;
1312
+ }
1313
+
1314
+ .cfg-act .cfg-note {
1315
+ margin: 0;
1316
+ }
1317
+
1318
+ .cfg-act .pill {
1319
+ text-decoration: none;
1320
+ cursor: pointer;
1321
+ }
1322
+
1323
+ .cfg-act .pill:disabled {
1324
+ cursor: default;
1325
+ }
1326
+
1327
+ .cfg-dot {
1328
+ flex: none;
1329
+ width: 9px;
1330
+ height: 9px;
1331
+ border-radius: 50%;
1332
+ background: var(--tone-minimal);
1333
+ }
1334
+
1335
+ .cfg-dot.bad {
1336
+ background: var(--tone-high);
1337
+ }
1338
+
1339
+ .cfg-dot.off {
1340
+ background: var(--text-faint);
1341
+ }
1342
+
865
1343
  /* ── mobile ──────────────────────────────────────────────────────────────────────────────────────── */
866
1344
 
867
1345
  @media (max-width: 899px) {
@@ -948,15 +1426,6 @@ input.filter {
948
1426
  margin: 6px 0 4px;
949
1427
  }
950
1428
 
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
1429
  /* THE rule that makes this read as a rated ladder instead of a bullet list: a fixed pill column and a
961
1430
  text column, so every meaning starts at the same left edge no matter how wide its band label is.
962
1431
  A flex row here lets each pill size itself and leaves the prose ragged — that is what the first
@@ -1000,69 +1469,306 @@ input.filter {
1000
1469
  .fw-bmrung {
1001
1470
  display: contents;
1002
1471
  }
1003
- .fw-bmrunglbl {
1004
- color: var(--text-body);
1005
- font-weight: 600;
1472
+ .fw-bmrunglbl {
1473
+ color: var(--text-body);
1474
+ font-weight: 600;
1475
+ }
1476
+ @media (max-width: 640px) {
1477
+ /* Stacked: one column, and the display:contents rows flow label-above-text in source order. */
1478
+ .fw-bmrungs {
1479
+ grid-template-columns: 1fr;
1480
+ row-gap: 1px;
1481
+ }
1482
+ }
1483
+
1484
+ .footer-demo-note {
1485
+ max-width: 420px; /* the note's width is the note's own, not its call site's */
1486
+ }
1487
+
1488
+ /* The line beside the primary action: what happens next when it is available, and why it is not when
1489
+ it is not. Capped here rather than at the call site for the same reason the note above is — width is
1490
+ a decision this file makes. Narrower than the note because it sits IN the action row, beside a
1491
+ button, rather than replacing it. */
1492
+ .footer-hint {
1493
+ max-width: 260px;
1494
+ font-size: 12px;
1495
+ line-height: 1.4;
1496
+ color: var(--text-muted);
1497
+ }
1498
+
1499
+ /* The acknowledgement a save leaves beside the button that was pressed.
1500
+ Quiet on purpose: it is a receipt, not an alert, and it sits in the same row as the primary action
1501
+ without competing with it. No new colour — `--tone-minimal` (the palette's clear-green) and the hairline are what the rest of
1502
+ this file already uses for a settled, good state. */
1503
+ .save-done {
1504
+ display: inline-flex;
1505
+ align-items: center;
1506
+ gap: 7px;
1507
+ font-size: 12.5px;
1508
+ color: var(--text-body);
1509
+ background: var(--surface-sunken);
1510
+ border: 1px solid var(--border-hairline);
1511
+ border-radius: 7px;
1512
+ padding: 7px 11px;
1513
+ }
1514
+ .save-done svg {
1515
+ color: var(--tone-minimal);
1516
+ flex: none;
1517
+ }
1518
+
1519
+ .fw-meta {
1520
+ font-size: 12px;
1521
+ color: var(--text-muted);
1522
+ margin-top: 4px;
1523
+ }
1524
+ .fw-meta b {
1525
+ color: var(--text-strong);
1526
+ }
1527
+
1528
+ /* Narrow viewports: the fixed pill column would squeeze the prose to a sliver, so the row stacks. */
1529
+ @media (max-width: 560px) {
1530
+ .fw-bmrow {
1531
+ grid-template-columns: 1fr;
1532
+ gap: 2px;
1533
+ padding: 6px 0;
1534
+ }
1535
+ }
1536
+
1537
+ /* ── Company settings: Profile, Projects, Search templates, New company ─────────────────────────────
1538
+ Each page is a stack of cards. The framework card leads Profile; Identity, Search defaults, the law
1539
+ firm fold and the background follow it, and New company draws the same cards in the same order. */
1540
+
1541
+ .card-stack {
1542
+ display: flex;
1543
+ flex-direction: column;
1544
+ gap: 14px;
1545
+ }
1546
+ .card-stack > .notice {
1547
+ margin: 0;
1548
+ }
1549
+ /* The fieldset that decides whether Profile can be changed is a stack in its own right, and a
1550
+ fieldset's own border, padding and minimum width are browser furniture nobody asked for. */
1551
+ fieldset.profile-cards {
1552
+ border: 0;
1553
+ padding: 0;
1554
+ margin: 0;
1555
+ min-width: 0;
1556
+ }
1557
+
1558
+ /* THE FOLD. A closed row whose chevron sits at its right edge and turns down when open — the shape every
1559
+ openable row on these pages takes, centre-aligned so the title, its note and the chevron share a line. */
1560
+ .fold-summary {
1561
+ display: flex;
1562
+ align-items: center;
1563
+ gap: 10px;
1564
+ list-style: none;
1565
+ cursor: pointer;
1566
+ font-weight: 700;
1567
+ font-size: 13.5px;
1568
+ color: var(--text-strong);
1569
+ }
1570
+ .fold-summary::-webkit-details-marker {
1571
+ display: none;
1572
+ }
1573
+ .fold-note {
1574
+ font-weight: 400;
1575
+ font-size: 12.5px;
1576
+ color: var(--text-muted);
1577
+ }
1578
+ .fold-chev {
1579
+ margin-left: auto;
1580
+ color: var(--text-faint);
1581
+ transition: transform 0.15s var(--ease-out);
1582
+ }
1583
+ details[open] > .fold-summary .fold-chev {
1584
+ transform: rotate(90deg);
1585
+ }
1586
+ @media (prefers-reduced-motion: reduce) {
1587
+ .fold-chev {
1588
+ transition: none;
1589
+ }
1590
+ }
1591
+ .fold-body {
1592
+ margin-top: 10px;
1593
+ }
1594
+ /* A fold inside a card: a hairline above it, so it reads as a row of the card rather than as a field. */
1595
+ .fw-fold {
1596
+ margin-top: 16px;
1597
+ border-top: 1px solid var(--border-hairline);
1598
+ padding-top: 12px;
1599
+ }
1600
+ /* A fold that IS a card: the whole card is the row until it opens. */
1601
+ .fold-card > .fold-summary {
1602
+ font-size: 14px;
1603
+ }
1604
+
1605
+ /* The configuration rows under the framework, in view whether the band detail is open or not. */
1606
+ .fw-rows {
1607
+ margin-top: 16px;
1608
+ border-top: 1px solid var(--border-hairline);
1609
+ padding-top: 12px;
1610
+ }
1611
+ .fw-row {
1612
+ display: flex;
1613
+ gap: 14px;
1614
+ padding: 5px 0;
1615
+ font-size: 13px;
1616
+ align-items: baseline;
1617
+ }
1618
+ .fw-row-label {
1619
+ color: var(--text-muted);
1620
+ min-width: 150px;
1621
+ }
1622
+ .fw-row-value {
1623
+ color: var(--text-strong);
1624
+ word-break: break-word;
1625
+ }
1626
+ .fw-foot {
1627
+ margin-top: 14px;
1628
+ }
1629
+
1630
+ /* One profile field: its label, its tag, its hint line, then the control. */
1631
+ .profile-field {
1632
+ display: block;
1633
+ margin-top: 18px;
1634
+ }
1635
+ .eyebrow + .profile-field,
1636
+ .eyebrow + div > .profile-field:first-child {
1637
+ margin-top: 12px;
1638
+ }
1639
+ .profile-field-label {
1640
+ display: block;
1641
+ font-weight: 700;
1642
+ color: var(--text-strong);
1643
+ font-size: 14px;
1644
+ }
1645
+ .profile-field-hint {
1646
+ display: block;
1647
+ font-size: 12.5px;
1648
+ color: var(--text-muted);
1649
+ margin: 2px 0 7px;
1650
+ }
1651
+ .profile-field-gap {
1652
+ display: block;
1653
+ height: 7px;
1654
+ }
1655
+
1656
+ /* REQUIRED AND OPTIONAL. Filled for the one a form cannot do without, quiet for the rest, so the eye finds
1657
+ the required field without reading every tag. Sized against the label it follows, and lowered a pixel
1658
+ so an uppercase tag sits on the label's line rather than above it. */
1659
+ .field-tag {
1660
+ display: inline-block;
1661
+ margin-left: 8px;
1662
+ padding: 1px 6px;
1663
+ border-radius: 5px;
1664
+ font-size: 10px;
1665
+ font-weight: 700;
1666
+ letter-spacing: 0.08em;
1667
+ line-height: 1.5;
1668
+ text-transform: uppercase;
1669
+ vertical-align: 1px;
1670
+ color: var(--text-faint);
1671
+ background: var(--surface-sunken);
1672
+ }
1673
+ .field-tag-required {
1674
+ color: var(--text-on-accent);
1675
+ background: var(--accent);
1676
+ }
1677
+
1678
+ /* The class search: the chosen classes as chips, then the box that finds one. */
1679
+ .chip-row {
1680
+ display: flex;
1681
+ align-items: center;
1682
+ gap: 6px;
1683
+ flex-wrap: wrap;
1684
+ }
1685
+ .class-picker > .chip-row {
1686
+ margin-top: 2px;
1006
1687
  }
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
- }
1688
+ .class-picker-box {
1689
+ position: relative;
1013
1690
  }
1014
1691
 
1015
- .footer-demo-note {
1016
- max-width: 420px; /* the note's width is the note's own, not its call site's */
1692
+ /* The New company rating card's one row: the framework a company starts on, and the way to its own. */
1693
+ .rating-row {
1694
+ display: flex;
1695
+ align-items: center;
1696
+ justify-content: space-between;
1697
+ gap: 12px;
1698
+ margin-top: 12px;
1699
+ }
1700
+ .rating-row-name {
1701
+ font-size: 14px;
1702
+ font-weight: 700;
1703
+ color: var(--text-strong);
1017
1704
  }
1018
1705
 
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;
1706
+ /* The page's one action row: the button, and the line that says what state it is in. */
1707
+ .row-foot {
1708
+ display: flex;
1709
+ align-items: center;
1710
+ gap: 12px;
1711
+ flex-wrap: wrap;
1712
+ margin-top: 18px;
1713
+ }
1714
+ .row-foot-note {
1715
+ font-size: 13px;
1027
1716
  color: var(--text-muted);
1028
1717
  }
1029
1718
 
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);
1719
+ /* A project row: the whole left side opens it; its menu and chevron sit at the right. */
1720
+ .row-card {
1721
+ padding: 12px 14px;
1722
+ border-radius: 10px;
1041
1723
  border: 1px solid var(--border-hairline);
1042
- border-radius: 7px;
1043
- padding: 7px 11px;
1724
+ background: var(--surface-raised);
1725
+ display: flex;
1726
+ align-items: center;
1727
+ gap: 12px;
1044
1728
  }
1045
- .save-done svg {
1046
- color: var(--tone-minimal);
1729
+ .row-card-open {
1730
+ flex: 1;
1731
+ min-width: 0;
1732
+ text-align: left;
1733
+ background: none;
1734
+ border: 0;
1735
+ padding: 0;
1736
+ cursor: pointer;
1737
+ font: inherit;
1738
+ color: inherit;
1739
+ }
1740
+ .row-card-name {
1741
+ font-weight: 700;
1742
+ color: var(--text-strong);
1743
+ }
1744
+ .row-chev {
1745
+ display: inline-flex;
1047
1746
  flex: none;
1747
+ color: var(--text-faint);
1048
1748
  }
1049
1749
 
1050
- .fw-meta {
1051
- font-size: 12px;
1052
- color: var(--text-muted);
1053
- margin-top: 4px;
1750
+ /* A row's "More actions" menu, under its button and aligned to its right edge. */
1751
+ .actions-menu {
1752
+ position: relative;
1753
+ flex: none;
1054
1754
  }
1055
- .fw-meta b {
1056
- color: var(--text-strong);
1755
+ .actions-menu-button {
1756
+ width: 28px;
1757
+ height: 28px;
1057
1758
  }
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
- }
1759
+ .actions-menu-list {
1760
+ position: absolute;
1761
+ right: 0;
1762
+ top: 32px;
1763
+ z-index: 30;
1764
+ min-width: 150px;
1765
+ padding: 6px;
1766
+ }
1767
+ .actions-menu-list .nav-item {
1768
+ font-size: 13px;
1769
+ padding: 7px 10px;
1770
+ margin: 0;
1771
+ white-space: nowrap;
1066
1772
  }
1067
1773
 
1068
1774
  /* ── the New clearance composer ──────────────────────────────────────────────────────────────────────
@@ -1227,20 +1933,7 @@ input.filter {
1227
1933
  text-decoration: underline;
1228
1934
  }
1229
1935
 
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
-
1936
+ /* A card to choose by: the rows of the company picker. */
1244
1937
  .entry-card {
1245
1938
  text-align: left;
1246
1939
  padding: 18px;
@@ -1310,35 +2003,8 @@ input.filter {
1310
2003
  color: var(--text-muted);
1311
2004
  }
1312
2005
 
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. */
2006
+ /* The column the form lives in. 720px, the design's measure for a form — NOT the footer's, which spans
2007
+ the screen. */
1342
2008
  /* ── §B — THE MEASURE MOVES OFF THE COLUMN AND ONTO ITS BLOCKS ────────────────────────────────────
1343
2009
  The owner: the New clearance page "is bound to an artificially narrow width; the table side-scrolls
1344
2010
  or wraps and looks worse than its content deserves. The top panel already fits the screen width."
@@ -1367,26 +2033,10 @@ input.filter {
1367
2033
  max-width: none;
1368
2034
  }
1369
2035
 
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
-
2036
+ /* ── §B — what a search carries, in or out at a glance ───────────────────────────────────────────────
2037
+ A list of three sentences of equal weight buried whether an axis ran at all in the clause after the
2038
+ dash. The tick and the cross carry the answer, on every search's row; the colour is a reinforcement,
2039
+ never the only signal, because a marker distinguishable by hue alone is not a marker for every reader. */
1390
2040
  .carries-mark {
1391
2041
  flex: none;
1392
2042
  width: 15px;
@@ -1544,50 +2194,6 @@ input.filter {
1544
2194
  background: var(--accent);
1545
2195
  }
1546
2196
 
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
2197
  .lever {
1592
2198
  display: flex;
1593
2199
  align-items: center;
@@ -1832,6 +2438,13 @@ input.filter {
1832
2438
  border-color: var(--text-strong);
1833
2439
  }
1834
2440
 
2441
+ /* The page header's buttons: the same two kinds, sized to sit beside a title. */
2442
+ .btn-sm {
2443
+ padding: 8px 14px;
2444
+ font-size: 13px;
2445
+ border-radius: 8px;
2446
+ }
2447
+
1835
2448
  /* ── the confirm dialog ─────────────────────────────────────────────────────────────────────────────
1836
2449
  The mandatory step. Nothing is spent until Start is pressed, and Back returns to a fully preserved
1837
2450
  form — so the scrim closes it rather than trapping anyone. */
@@ -1877,7 +2490,10 @@ input.filter {
1877
2490
  }
1878
2491
 
1879
2492
  .stop-choice-opt {
1880
- display: block;
2493
+ display: grid;
2494
+ grid-template-columns: auto 1fr;
2495
+ column-gap: 10px;
2496
+ align-items: baseline;
1881
2497
  width: 100%;
1882
2498
  text-align: left;
1883
2499
  padding: 13px 15px;
@@ -1892,7 +2508,15 @@ input.filter {
1892
2508
  border-color: var(--accent-ink);
1893
2509
  }
1894
2510
 
2511
+ /* The radio is the row's own control, drawn in the accent so the selected option reads at a glance. */
2512
+ .stop-choice-opt input {
2513
+ grid-row: 1 / span 2;
2514
+ margin: 2px 0 0;
2515
+ accent-color: var(--accent);
2516
+ }
2517
+
1895
2518
  .stop-choice-opt b {
2519
+ grid-column: 2;
1896
2520
  display: block;
1897
2521
  font-size: 14px;
1898
2522
  color: var(--text-strong);
@@ -1900,6 +2524,7 @@ input.filter {
1900
2524
  }
1901
2525
 
1902
2526
  .stop-choice-opt span {
2527
+ grid-column: 2;
1903
2528
  display: block;
1904
2529
  font-size: 12.5px;
1905
2530
  line-height: 1.5;
@@ -1910,6 +2535,12 @@ input.filter {
1910
2535
  border-color: var(--tone-medium);
1911
2536
  }
1912
2537
 
2538
+ /* THE SELECTED OPTION IS THE ONE THE BUTTON NAMES, so it carries the same accent the button does. */
2539
+ .stop-choice-opt.selected {
2540
+ border-color: var(--accent);
2541
+ background: var(--accent-wash);
2542
+ }
2543
+
1913
2544
  .modal-rule {
1914
2545
  position: absolute;
1915
2546
  left: 0;
@@ -1968,6 +2599,78 @@ input.filter {
1968
2599
  .home2-band-count { font-size: 11.5px; color: var(--text-faint); white-space: nowrap; }
1969
2600
  .home2-band-rule { height: 1px; background: var(--border-hairline); }
1970
2601
 
2602
+ /* THE ALLOWANCE LINE. Quiet by construction — no band, no heading, no colour — because it states a fact
2603
+ about the account rather than a fault with the form. It appears only at five searches or fewer. */
2604
+ .nc-allowance { margin: -6px 0 18px; font-size: 13px; color: var(--text-muted); }
2605
+
2606
+ /* ── New clearance: one form, top to bottom ──────────────────────────────────────────────────────────
2607
+ Quiet additions to the kit the form already used. The recommendation tag is the one accent on a row
2608
+ that is not selected, because it is the one thing on it the reader did not choose. */
2609
+ .nc-company { max-width: 340px; }
2610
+ .nc-company-name { font-size: 15px; font-weight: 600; color: var(--text-strong); }
2611
+ .nc-project { max-width: 340px; flex: 1; }
2612
+ .nc-describe-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
2613
+ .nc-optional { font-size: 11.5px; color: var(--text-faint); }
2614
+ .nc-which-head {
2615
+ display: flex;
2616
+ align-items: flex-end;
2617
+ justify-content: space-between;
2618
+ gap: 12px;
2619
+ flex-wrap: wrap;
2620
+ margin-bottom: 10px;
2621
+ }
2622
+ .nc-templates { display: inline-flex; align-items: flex-end; gap: 8px; }
2623
+ .nc-templates label { display: block; }
2624
+ .nc-templates select { min-width: 190px; }
2625
+ .nc-template-line { margin: 0 0 10px; font-size: 12.5px; line-height: 1.6; color: var(--text-muted); }
2626
+
2627
+ /* A small action beside the thing it acts on — Manage, Manage projects, Clear template, Show all. */
2628
+ .chip-btn {
2629
+ display: inline-flex;
2630
+ align-items: center;
2631
+ padding: 3px 10px;
2632
+ border-radius: 999px;
2633
+ border: 1px solid var(--border-hairline);
2634
+ background: var(--surface-sunken);
2635
+ color: var(--text-muted);
2636
+ font-family: var(--font-body);
2637
+ font-size: 11px;
2638
+ font-weight: 700;
2639
+ white-space: nowrap;
2640
+ cursor: pointer;
2641
+ }
2642
+ .chip-btn:hover { border-color: var(--accent-ink); color: var(--text-accent); }
2643
+
2644
+ /* The search row as a card holding its pick button and, when selected, its own option. */
2645
+ .pick-row-stack { flex-direction: column; align-items: stretch; gap: 10px; }
2646
+ .pick-row-hit {
2647
+ display: flex;
2648
+ align-items: flex-start;
2649
+ gap: 12px;
2650
+ width: 100%;
2651
+ padding: 0;
2652
+ border: 0;
2653
+ background: none;
2654
+ font: inherit;
2655
+ color: inherit;
2656
+ text-align: left;
2657
+ cursor: pointer;
2658
+ }
2659
+ .pick-row-hit:disabled { cursor: default; }
2660
+ .pick-row-inside { margin-left: 27px; }
2661
+ .pick-tag {
2662
+ padding: 1px 8px;
2663
+ border-radius: 999px;
2664
+ border: 1px solid var(--accent-ink);
2665
+ background: var(--accent-wash);
2666
+ color: var(--text-accent);
2667
+ font-size: 10.5px;
2668
+ font-weight: 700;
2669
+ }
2670
+ .pick-row-reason { display: block; margin-top: 3px; font-size: 12px; color: var(--text-body); }
2671
+ .pick-chips { display: flex; flex-wrap: wrap; gap: 3px 16px; margin-top: 6px; font-size: 12px; color: var(--text-muted); }
2672
+ .pick-chips > span { display: inline-flex; align-items: baseline; gap: 6px; }
2673
+
1971
2674
  /* A RUNNING RUN AS A ROW. The queue's grid, with the two columns a running row has and a waiting one
1972
2675
  does not. Declared as a modifier rather than a new grid so the narrow-screen rule that hides columns
1973
2676
  keeps one definition for both. */
@@ -2120,6 +2823,20 @@ input.filter {
2120
2823
  margin: 0 0 10px; font-size: 12.5px; font-weight: 600; letter-spacing: .02em; color: var(--text-muted);
2121
2824
  }
2122
2825
 
2826
+ /* THE FOLDS: the key door beside a sign-in door, and "Setup steps" once connected. Both open only when the
2827
+ reader opens them; the summary is the heading, with a chevron that turns when open. */
2828
+ .steps-alt-fold > summary,
2829
+ .steps-fold > summary { display: flex; align-items: center; gap: 10px; list-style: none; cursor: pointer; }
2830
+ .steps-alt-fold > summary::-webkit-details-marker,
2831
+ .steps-fold > summary::-webkit-details-marker { display: none; }
2832
+ .steps-alt-fold > summary .steps-alt-head,
2833
+ .steps-fold > summary .steps-alt-head { margin: 0; }
2834
+ .fold-chev { margin-left: auto; flex: none; color: var(--text-faint); transition: transform 0.15s var(--ease-out); }
2835
+ .steps-alt-fold[open] > summary .fold-chev,
2836
+ .steps-fold[open] > summary > .fold-chev { transform: rotate(90deg); }
2837
+ .steps-alt-fold > ol.steps { margin-top: 14px; }
2838
+ .steps-fold-body { margin-top: 14px; }
2839
+
2123
2840
  .home2-acked { margin-top: 13px; }
2124
2841
  .home2-acked-toggle {
2125
2842
  padding: 0; background: none; border: 0; cursor: pointer; font: inherit; font-size: 12.5px;
@@ -2304,7 +3021,24 @@ input.filter {
2304
3021
 
2305
3022
  .ai-connect { margin-top: 34px; }
2306
3023
 
2307
- .ai-screen .ai-connect-head { margin: 0 0 12px; }
3024
+ .ai-screen .ai-connect-head { margin: 0; }
3025
+
3026
+ /* "Connect it" and, beside it, whether this reader's assistant has been seen calling. */
3027
+ .ai-connect-top { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
3028
+ .conn-state {
3029
+ display: inline-flex; align-items: center; gap: 7px; padding: 3px 11px 3px 9px;
3030
+ border: 1px solid var(--border-hairline); border-radius: 999px; background: var(--surface-raised);
3031
+ font-size: 12.5px; color: var(--text-muted);
3032
+ }
3033
+ .conn-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
3034
+ .conn-dot-on { background: var(--tone-minimal); }
3035
+
3036
+ /* "Optional", quietly, at the end of the standfirst — never the word the page opens with. */
3037
+ .lede-opt {
3038
+ display: inline-block; margin-left: 6px; padding: 2px 7px; vertical-align: 2px; border-radius: 5px;
3039
+ background: var(--surface-sunken); font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
3040
+ text-transform: uppercase; color: var(--text-faint);
3041
+ }
2308
3042
 
2309
3043
  /* ── THE WHERE-CHOICE ─────────────────────────────────────────────────────────────────────────────
2310
3044
  ONE control, the same size before and after a pick. Selection is shown the way every other choice in
@@ -2370,6 +3104,13 @@ input.filter {
2370
3104
  card can show — so neither picking an app nor pressing Copy moves anything around it. */
2371
3105
  .ai-slot { position: relative; min-height: 180px; }
2372
3106
 
3107
+ /* A closed fold reserves nothing: the reserved height is for steps that are drawn. */
3108
+ .ai-slot.is-folded { min-height: 0; }
3109
+
3110
+ /* The way back to the report the reader came from, above the steps. */
3111
+ .back-to-report { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
3112
+ .back-to-report-line { font-size: 13.5px; color: var(--text-muted); }
3113
+
2373
3114
  /* The hidden copy of every panel the slot's height is taken from. Laid out at the slot's own width, never
2374
3115
  seen, never reached by a pointer or a keyboard. */
2375
3116
  .ai-probe {
@@ -2486,6 +3227,9 @@ ol.steps > li::before {
2486
3227
 
2487
3228
  .secret-btn { margin-top: 8px; }
2488
3229
 
3230
+ /* A folded list's buttons are quiet: the steps above the fold are the way in. */
3231
+ .ai-screen .btn-sm { padding: 6px 11px; font-size: 12.5px; }
3232
+
2489
3233
  /* THE PRESSED CONTROL IS THE CONFIRMATION: solid accent on the button pressed, never a muted caption. */
2490
3234
  .is-copied,
2491
3235
  .is-copied:hover,
@@ -2530,8 +3274,31 @@ ol.steps > li::before {
2530
3274
 
2531
3275
  .ai-help p { margin: 6px 0 0; color: var(--text-muted); font-size: 13.5px; line-height: 1.55; }
2532
3276
 
3277
+ .ai-help-guide { font-size: 12px; text-decoration: none; cursor: pointer; }
3278
+
3279
+
3280
+ /* ── People: what the Permissions column's words mean, under the column ─────────────────────────────────
3281
+ A two-column key, term beside meaning, so each word lines up with the sentence it stands for. */
3282
+ .perm-key {
3283
+ display: grid;
3284
+ grid-template-columns: auto 1fr;
3285
+ gap: 3px 14px;
3286
+ margin: 14px 0 0;
3287
+ font-size: 12.5px;
3288
+ color: var(--text-muted);
3289
+ }
3290
+
3291
+ .perm-key dt {
3292
+ color: var(--text-strong);
3293
+ font-weight: 600;
3294
+ white-space: nowrap;
3295
+ }
3296
+
3297
+ .perm-key dd {
3298
+ margin: 0;
3299
+ }
2533
3300
 
2534
- /* ── Give someone access: the points on the tree a new person can be given ─────────────────────────────
3301
+ /* ── Give access: the points on the tree a new person can be given ───────────────────────────────────────
2535
3302
  One row per point. A BUTTON, because choosing a point is an action and a keyboard has to reach it; the
2536
3303
  resets below are what make a button read as a row. `.sub` indents a company under the organisation
2537
3304
  it sits in. A row covered by a wider choice above it — everything, or its whole organisation — is