@inkbar/theme 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/package.json +1 -1
  3. package/theme.css +693 -19
package/README.md CHANGED
@@ -83,6 +83,21 @@ The theme gives a toolbar a background and padding and styles the controls in it
83
83
  }
84
84
  ```
85
85
 
86
+ ## Your own controls
87
+
88
+ Two classes give your own controls the look of the built-in ones. Add `ink-select` to a `<select>` to give it the zoom select's box and chevron. To put an icon in a field, wrap the input in `ink-input-group` and add an `ink-input-accessory` before it for an icon at the start, or after it for one at the end. The icon lets clicks through to the field, and the input makes room for it. The find bar and the search results field use this for their search icon.
89
+
90
+ ```html
91
+ <select class="ink-select">
92
+ …
93
+ </select>
94
+
95
+ <div class="ink-input-group">
96
+ <span class="ink-input-accessory"><svg aria-hidden="true">…</svg></span>
97
+ <input type="text" aria-label="Filter notes" />
98
+ </div>
99
+ ```
100
+
86
101
  ## Licence
87
102
 
88
103
  [MIT](https://github.com/inkbar/inkbar/blob/main/LICENSE). From the team behind [Redactr](https://redactr.io).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkbar/theme",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Default stylesheet for Inkbar. Zero specificity, so your CSS wins.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://inkbar.dev",
package/theme.css CHANGED
@@ -11,9 +11,13 @@
11
11
  * new annotations only.
12
12
  */
13
13
  @layer inkbar {
14
- /* Tokens. Popovers are portalled out of the root, so they carry the tokens too. */
14
+ /*
15
+ * Tokens. Popovers are portalled out of the root, and a sidebar may sit outside it, so they
16
+ * carry the tokens too.
17
+ */
15
18
  :where(
16
19
  [data-ink-root],
20
+ [data-ink-sidebar],
17
21
  [data-ink-thread-popover],
18
22
  [data-ink-popover],
19
23
  [data-ink-select-content],
@@ -33,6 +37,12 @@
33
37
  /* Pages stay white in dark mode and the search layer multiplies, so these serve both. */
34
38
  --ink-search-hit: #f1ffa0;
35
39
  --ink-search-current: #e4ff4a;
40
+ /* The selected record's mark; pages stay white in dark mode, so this serves both. */
41
+ --ink-mark-selected-outline: 2px solid #2740f5;
42
+ /* Sidebars (createSidebar and the bindings' sidebar): the open sidebar and its rail. */
43
+ --ink-sidebar-width: 280px;
44
+ --ink-sidebar-rail-width: 48px;
45
+ --ink-thumbnail-width: 120px;
36
46
 
37
47
  --ink-bg: var(--muted, #f1f2f5);
38
48
  --ink-fg: var(--foreground, #151820);
@@ -44,6 +54,8 @@
44
54
  --ink-ring: var(--ring, #2b5a9e);
45
55
  --ink-radius: var(--radius, 0.5rem);
46
56
  --ink-page-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
57
+ /* Thumbnails are small, so the page shadow reads heavy on them: a hairline edge and a soft lift. */
58
+ --ink-thumbnail-shadow: 0 0 0 1px rgb(0 0 0 / 0.05), 0 1px 2px rgb(0 0 0 / 0.08);
47
59
  --ink-text-selection: color-mix(in srgb, AccentColor, transparent 50%);
48
60
  /* Form fields sit on the white page, in light and dark alike. */
49
61
  --ink-form-field-bg: rgb(43 90 158 / 0.12);
@@ -56,6 +68,7 @@
56
68
  @media (prefers-color-scheme: dark) {
57
69
  :where(
58
70
  [data-ink-root]:not(.light, .light *),
71
+ [data-ink-sidebar]:not(.light, .light *),
59
72
  [data-ink-thread-popover]:not(.light *),
60
73
  [data-ink-popover]:not(.light *),
61
74
  [data-ink-select-content]:not(.light *),
@@ -71,6 +84,7 @@
71
84
  --ink-accent-fg: var(--primary-foreground, #151820);
72
85
  --ink-ring: var(--ring, #7aa5e6);
73
86
  --ink-page-shadow: 0 1px 3px rgb(0 0 0 / 0.6);
87
+ --ink-thumbnail-shadow: 0 1px 2px rgb(0 0 0 / 0.3);
74
88
  --ink-form-field-bg: rgb(43 90 158 / 0.16);
75
89
  --ink-form-field-focus: #3d73c4;
76
90
  --ink-form-field-invalid: #c0392b;
@@ -81,6 +95,8 @@
81
95
  :where(
82
96
  .dark [data-ink-root]:not(.light, .dark .light *),
83
97
  [data-ink-root].dark,
98
+ .dark [data-ink-sidebar]:not(.light, .dark .light *),
99
+ [data-ink-sidebar].dark,
84
100
  .dark [data-ink-thread-popover]:not(.dark .light *),
85
101
  .dark [data-ink-popover]:not(.dark .light *),
86
102
  .dark [data-ink-select-content]:not(.dark .light *),
@@ -96,6 +112,7 @@
96
112
  --ink-accent-fg: var(--primary-foreground, #151820);
97
113
  --ink-ring: var(--ring, #7aa5e6);
98
114
  --ink-page-shadow: 0 1px 3px rgb(0 0 0 / 0.6);
115
+ --ink-thumbnail-shadow: 0 1px 2px rgb(0 0 0 / 0.3);
99
116
  --ink-form-field-bg: rgb(43 90 158 / 0.16);
100
117
  --ink-form-field-focus: #3d73c4;
101
118
  --ink-form-field-invalid: #c0392b;
@@ -367,6 +384,11 @@
367
384
  pointer-events: auto;
368
385
  }
369
386
 
387
+ /* The mark of the record selected in a list, the page or the host app. */
388
+ :where(.ink-plugin-layer > button[data-selected]) {
389
+ outline: var(--ink-mark-selected-outline);
390
+ }
391
+
370
392
  /* Form fields: over the text layer, under the annotations (after the overlay in the page, at
371
393
  the same level), so a press on an annotation's shape is the annotation's. A field is
372
394
  tinted, except while it is read-only or signed or highlighting is off. It is ringed while
@@ -481,6 +503,14 @@
481
503
  cursor: default;
482
504
  }
483
505
 
506
+ /* A locked note still opens its thread on a click. */
507
+ :where(
508
+ [data-ink-tool='select'] .ink-annotation[data-locked][data-type='text'],
509
+ [data-ink-tool='select-text'] .ink-annotation[data-locked][data-type='text']
510
+ ) {
511
+ cursor: pointer;
512
+ }
513
+
484
514
  /* While an annotation is moved the whole viewport shows it, over any element's own cursor.
485
515
  The one rule here that is not zero specificity: it has to beat them. */
486
516
  [data-ink-dragging],
@@ -522,14 +552,12 @@
522
552
  outline-style: dotted;
523
553
  }
524
554
 
555
+ /* The icon fills the note's box, so it scales with the zoom as the PDF's own icons do. */
525
556
  :where(.ink-note-icon) {
526
557
  position: absolute;
527
- left: 0;
528
- top: 0;
558
+ inset: 0;
529
559
  display: block;
530
- width: 20px;
531
- height: 20px;
532
- border-radius: 3px 3px 3px 0;
560
+ border-radius: 15% 15% 15% 0;
533
561
  background: var(--ink-note);
534
562
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.3);
535
563
  pointer-events: none;
@@ -802,6 +830,373 @@
802
830
  outline-offset: 2px;
803
831
  }
804
832
 
833
+ /*
834
+ * Sidebars (createSidebar and the bindings' sidebar): a plain block the app places with its
835
+ * own layout. --ink-sidebar-width sets the open width and --ink-sidebar-rail-width the rail's.
836
+ * The border and the rail sit on the inner edge, beside the document: data-side="right"
837
+ * mirrors them.
838
+ */
839
+ :where(.ink-sidebar) {
840
+ box-sizing: border-box;
841
+ display: flex;
842
+ flex-direction: column;
843
+ width: var(--ink-sidebar-width);
844
+ min-height: 0;
845
+ border-right: 1px solid var(--ink-border);
846
+ background: var(--ink-surface);
847
+ color: var(--ink-fg);
848
+ }
849
+
850
+ :where(.ink-sidebar[data-side='right']) {
851
+ border-right: 0;
852
+ border-left: 1px solid var(--ink-border);
853
+ }
854
+
855
+ /* The rail: icon buttons beside the document, so they stay put as the panel opens and closes;
856
+ the sidebar narrows to the rail with no panel open. The rail comes first in the markup. */
857
+ :where(.ink-sidebar[data-layout='rail']) {
858
+ flex-direction: row-reverse;
859
+ width: auto;
860
+ }
861
+
862
+ :where(.ink-sidebar[data-layout='rail'][data-side='right']) {
863
+ flex-direction: row;
864
+ }
865
+
866
+ :where(.ink-sidebar-rail) {
867
+ display: flex;
868
+ flex: none;
869
+ flex-direction: column;
870
+ gap: 0.25rem;
871
+ box-sizing: border-box;
872
+ width: var(--ink-sidebar-rail-width);
873
+ padding: 0.375rem 0;
874
+ align-items: center;
875
+ }
876
+
877
+ :where(.ink-sidebar[data-layout='rail'] .ink-sidebar-panels) {
878
+ width: calc(var(--ink-sidebar-width) - var(--ink-sidebar-rail-width));
879
+ border-right: 1px solid var(--ink-border);
880
+ }
881
+
882
+ :where(.ink-sidebar[data-layout='rail'][data-side='right'] .ink-sidebar-panels) {
883
+ border-right: 0;
884
+ border-left: 1px solid var(--ink-border);
885
+ }
886
+
887
+ :where(.ink-sidebar-tabs) {
888
+ display: flex;
889
+ flex: none;
890
+ border-bottom: 1px solid var(--ink-border);
891
+ }
892
+
893
+ :where(.ink-sidebar-panels) {
894
+ display: flex;
895
+ flex: 1;
896
+ flex-direction: column;
897
+ min-width: 0;
898
+ min-height: 0;
899
+ }
900
+
901
+ /* After the rule above, which it overrides at the same specificity. */
902
+ :where(.ink-sidebar[data-layout='rail'][data-state='closed'] .ink-sidebar-panels) {
903
+ display: none;
904
+ }
905
+
906
+ /* Tabs collapse as a whole, strip and all: the host's own button sets `open` to reopen it. */
907
+ :where(.ink-sidebar[data-layout='tabs'][data-state='closed']) {
908
+ display: none;
909
+ }
910
+
911
+ :where(.ink-sidebar-panel) {
912
+ flex: 1;
913
+ min-height: 0;
914
+ overflow: auto;
915
+ }
916
+
917
+ :where(.ink-sidebar-panel[hidden]) {
918
+ display: none;
919
+ }
920
+
921
+ /* Not visible: the whole sidebar goes, its state kept. After every display rule above. */
922
+ :where(.ink-sidebar[hidden]) {
923
+ display: none;
924
+ }
925
+
926
+ /* The panel's title shows in the rail only, where the buttons carry no text: a strip as tall
927
+ as the rail is wide, so it lines up with the first button. Tabs and stack name the panel in
928
+ their own triggers, and there the body steps out of the layout. */
929
+ :where(.ink-sidebar-panel-title) {
930
+ display: none;
931
+ }
932
+
933
+ :where(.ink-sidebar-panel-body) {
934
+ display: contents;
935
+ }
936
+
937
+ :where(.ink-sidebar-panel[data-layout='rail']:not([hidden])) {
938
+ display: flex;
939
+ flex-direction: column;
940
+ overflow: hidden;
941
+ }
942
+
943
+ :where(.ink-sidebar-panel[data-layout='rail'] > .ink-sidebar-panel-title) {
944
+ display: flex;
945
+ flex: none;
946
+ align-items: center;
947
+ box-sizing: border-box;
948
+ min-height: var(--ink-sidebar-rail-width);
949
+ padding: 0 1rem;
950
+ border-bottom: 1px solid var(--ink-border);
951
+ font-size: 0.875rem;
952
+ font-weight: 600;
953
+ }
954
+
955
+ :where(.ink-sidebar-panel[data-layout='rail'] > .ink-sidebar-panel-body) {
956
+ display: block;
957
+ flex: 1;
958
+ min-height: 0;
959
+ overflow: auto;
960
+ }
961
+
962
+ /* The bindings wrap each panel in .ink-sidebar-slot, the stack's section in that layout. In
963
+ rail and tabs it steps out of the layout, so the panel fills the panels area. */
964
+ :where(.ink-sidebar-panels > .ink-sidebar-slot) {
965
+ display: contents;
966
+ }
967
+
968
+ /* The stack scrolls as a whole; each expanded panel takes the height of its content. */
969
+ :where(.ink-sidebar[data-layout='stack']) {
970
+ overflow: auto;
971
+ }
972
+
973
+ :where(.ink-sidebar-section) {
974
+ border-bottom: 1px solid var(--ink-border);
975
+ }
976
+
977
+ :where(.ink-sidebar-section > .ink-sidebar-panel) {
978
+ overflow: visible;
979
+ }
980
+
981
+ :where(.ink-sidebar-rail button, .ink-sidebar-tabs button, .ink-sidebar-header) {
982
+ position: relative;
983
+ display: inline-flex;
984
+ align-items: center;
985
+ gap: 0.5rem;
986
+ box-sizing: border-box;
987
+ min-width: 44px;
988
+ min-height: 44px;
989
+ padding: 0 0.75rem;
990
+ border: 0;
991
+ border-radius: calc(var(--ink-radius) - 2px);
992
+ background: transparent;
993
+ color: inherit;
994
+ font: inherit;
995
+ cursor: pointer;
996
+ }
997
+
998
+ /* Rail buttons match the toolbar's (--ink-control-size); a halo keeps the 44px hit area. */
999
+ :where(.ink-sidebar-rail button) {
1000
+ justify-content: center;
1001
+ width: var(--ink-control-size, 2.25rem);
1002
+ min-width: 0;
1003
+ height: var(--ink-control-size, 2.25rem);
1004
+ min-height: 0;
1005
+ padding: 0;
1006
+ }
1007
+
1008
+ :where(.ink-sidebar-rail button)::after {
1009
+ content: '';
1010
+ position: absolute;
1011
+ inset: -4px;
1012
+ }
1013
+
1014
+ :where(.ink-sidebar-tabs button) {
1015
+ flex: 1;
1016
+ justify-content: center;
1017
+ border-radius: 0;
1018
+ border-bottom: 2px solid transparent;
1019
+ }
1020
+
1021
+ :where(.ink-sidebar-header) {
1022
+ width: 100%;
1023
+ border-radius: 0;
1024
+ text-align: start;
1025
+ font-weight: 600;
1026
+ }
1027
+
1028
+ :where(
1029
+ .ink-sidebar-rail button:hover,
1030
+ .ink-sidebar-tabs button:hover,
1031
+ .ink-sidebar-header:hover
1032
+ ) {
1033
+ background: color-mix(in srgb, var(--ink-fg) 8%, transparent);
1034
+ }
1035
+
1036
+ :where(.ink-sidebar-rail button[aria-selected='true']) {
1037
+ background: var(--ink-accent);
1038
+ color: var(--ink-accent-fg);
1039
+ }
1040
+
1041
+ :where(.ink-sidebar-tabs button[aria-selected='true']) {
1042
+ border-bottom-color: var(--ink-accent);
1043
+ }
1044
+
1045
+ :where(
1046
+ .ink-sidebar-rail button:focus-visible,
1047
+ .ink-sidebar-tabs button:focus-visible,
1048
+ .ink-sidebar-header:focus-visible
1049
+ ) {
1050
+ outline: 2px solid var(--ink-ring);
1051
+ outline-offset: -2px;
1052
+ }
1053
+
1054
+ :where(.ink-sidebar-badge) {
1055
+ min-width: 1.125rem;
1056
+ padding: 0 0.3125rem;
1057
+ border-radius: 999px;
1058
+ background: var(--ink-accent);
1059
+ color: var(--ink-accent-fg);
1060
+ font-size: 0.75rem;
1061
+ line-height: 1.125rem;
1062
+ text-align: center;
1063
+ font-variant-numeric: tabular-nums;
1064
+ }
1065
+
1066
+ :where(.ink-sidebar-header .ink-sidebar-badge) {
1067
+ margin-inline-start: auto;
1068
+ }
1069
+
1070
+ /* On the rail the badge sits on the button's corner. */
1071
+ :where(.ink-sidebar-rail .ink-sidebar-badge) {
1072
+ position: absolute;
1073
+ top: -5px;
1074
+ right: -5px;
1075
+ z-index: 1;
1076
+ box-shadow: 0 0 0 2px var(--ink-surface);
1077
+ }
1078
+
1079
+ :where(.ink-sidebar-rail button[aria-selected='true'] .ink-sidebar-badge) {
1080
+ background: var(--ink-accent-fg);
1081
+ color: var(--ink-accent);
1082
+ }
1083
+
1084
+ :where(.ink-sidebar-badge[hidden]) {
1085
+ display: none;
1086
+ }
1087
+
1088
+ /* Thumbnails: a page as paper, its placeholder the same shape until it renders. The painter
1089
+ sets the size inline; the layers inside are inert copies. */
1090
+ :where(.ink-thumbnail) {
1091
+ position: relative;
1092
+ display: block;
1093
+ flex: none;
1094
+ overflow: hidden;
1095
+ background: #ffffff;
1096
+ box-shadow: var(--ink-thumbnail-shadow);
1097
+ }
1098
+
1099
+ :where(.ink-thumbnail-annotation > svg) {
1100
+ position: absolute;
1101
+ left: 0;
1102
+ top: 0;
1103
+ }
1104
+
1105
+ /* A note's icon fills its box instead of keeping its on-page size; comment glyphs, which keep
1106
+ a fixed size at every zoom, would cover a small page, so they are left out. */
1107
+ :where(.ink-thumbnail-annotation .ink-note-icon) {
1108
+ box-shadow: none;
1109
+ }
1110
+
1111
+ :where(.ink-thumbnail-annotation .ink-comment-marker) {
1112
+ display: none;
1113
+ }
1114
+
1115
+ /*
1116
+ * The thumbnail list (createThumbnails and the bindings' list): it fills its parent's height
1117
+ * and scrolls, with only the items near the visible part mounted, each placed absolutely at
1118
+ * its offset over a spacer of the full height. The controller fits each thumbnail to the list
1119
+ * less a 16px gutter each side, and counts an item as its thumbnail plus 38px: the 16px gutter
1120
+ * above, a 6px gap and the 16px number. The list's own 16px padding closes the last one.
1121
+ */
1122
+ /* The list is a small viewport: pages on the same canvas, with the same shadow. */
1123
+ :where(.ink-thumbnails) {
1124
+ position: relative;
1125
+ box-sizing: border-box;
1126
+ height: 100%;
1127
+ padding-bottom: 16px;
1128
+ overflow-x: hidden;
1129
+ overflow-y: auto;
1130
+ /* A scrollbar takes the same room on both sides, so the gutters stay even. */
1131
+ scrollbar-gutter: stable both-edges;
1132
+ background: var(--ink-bg);
1133
+ }
1134
+
1135
+ :where(.ink-thumbnails-entry) {
1136
+ position: absolute;
1137
+ left: 0;
1138
+ right: 0;
1139
+ display: flex;
1140
+ justify-content: center;
1141
+ }
1142
+
1143
+ :where(.ink-thumbnails-item) {
1144
+ box-sizing: border-box;
1145
+ display: flex;
1146
+ flex-direction: column;
1147
+ align-items: center;
1148
+ gap: 6px;
1149
+ width: 100%;
1150
+ height: 100%;
1151
+ padding: 16px 16px 0;
1152
+ border: 0;
1153
+ border-radius: var(--ink-radius);
1154
+ background: transparent;
1155
+ color: inherit;
1156
+ font: inherit;
1157
+ cursor: pointer;
1158
+ }
1159
+
1160
+ /* The page carries the states, as the main view's pages do: a ring on hover, the accent ring
1161
+ on the current page, and its number in the accent like a pressed toolbar button. */
1162
+ :where(.ink-thumbnails-item:hover .ink-thumbnail) {
1163
+ box-shadow:
1164
+ var(--ink-thumbnail-shadow),
1165
+ 0 0 0 2px color-mix(in srgb, var(--ink-fg) 20%, transparent);
1166
+ }
1167
+
1168
+ :where(.ink-thumbnails-item:focus-visible) {
1169
+ outline: none;
1170
+ }
1171
+
1172
+ :where(.ink-thumbnails-item:focus-visible .ink-thumbnail) {
1173
+ outline: 2px solid var(--ink-ring);
1174
+ outline-offset: 3px;
1175
+ }
1176
+
1177
+ :where(.ink-thumbnails-item[aria-current='page'] .ink-thumbnail) {
1178
+ box-shadow:
1179
+ var(--ink-thumbnail-shadow),
1180
+ 0 0 0 2px var(--ink-accent);
1181
+ }
1182
+
1183
+ :where(.ink-thumbnails-label) {
1184
+ min-width: 1.5rem;
1185
+ padding: 0 0.375rem;
1186
+ border-radius: 999px;
1187
+ color: var(--ink-muted-fg);
1188
+ font-size: 0.75rem;
1189
+ line-height: 16px;
1190
+ text-align: center;
1191
+ font-variant-numeric: tabular-nums;
1192
+ }
1193
+
1194
+ :where(.ink-thumbnails-item[aria-current='page'] .ink-thumbnails-label) {
1195
+ background: var(--ink-accent);
1196
+ color: var(--ink-accent-fg);
1197
+ font-weight: 600;
1198
+ }
1199
+
805
1200
  /* Text for assistive technology only, such as the find bar's live region. */
806
1201
  :where(.ink-visually-hidden) {
807
1202
  position: absolute;
@@ -848,41 +1243,40 @@
848
1243
  }
849
1244
 
850
1245
  /*
851
- * Zoom select. The bindings render a button trigger (`.ink-select-trigger`) and a portalled
852
- * listbox; createViewer renders a native select. Both take the same box and chevron.
1246
+ * Select. `.ink-select` is the toolbar's select box, for a native select or a button trigger
1247
+ * (`.ink-select-trigger`) with a portalled listbox. The zoom select uses it in every binding,
1248
+ * and an app's own toolbar select takes the class to match.
853
1249
  */
854
- :where(.ink-toolbar .ink-zoom-select) {
1250
+ :where(.ink-select) {
855
1251
  box-sizing: border-box;
856
1252
  display: inline-flex;
857
1253
  align-items: center;
858
1254
  justify-content: space-between;
859
1255
  gap: 0.5rem;
860
- width: 7.5rem;
861
1256
  height: var(--ink-control-size, 2.25rem);
862
1257
  padding: 0 0.5rem 0 0.75rem;
863
1258
  border: 1px solid var(--ink-border);
864
1259
  border-radius: calc(var(--ink-radius) - 2px);
865
- background: var(--ink-surface);
1260
+ background-color: var(--ink-surface);
866
1261
  color: inherit;
867
1262
  font: inherit;
868
1263
  font-size: 0.875rem;
869
- font-variant-numeric: tabular-nums;
870
1264
  text-align: left;
871
1265
  white-space: nowrap;
872
1266
  cursor: pointer;
873
1267
  }
874
1268
 
875
- :where(.ink-toolbar .ink-zoom-select:hover) {
876
- background: color-mix(in srgb, var(--ink-fg) 4%, var(--ink-surface));
1269
+ :where(.ink-select:hover) {
1270
+ background-color: color-mix(in srgb, var(--ink-fg) 4%, var(--ink-surface));
877
1271
  }
878
1272
 
879
- :where(.ink-toolbar .ink-zoom-select:disabled) {
1273
+ :where(.ink-select:disabled) {
880
1274
  opacity: 0.5;
881
1275
  cursor: default;
882
1276
  }
883
1277
 
884
- /* The native select draws the same chevron as the trigger's icon: Lucide's ChevronDown; see LICENSE. */
885
- :where(.ink-toolbar select.ink-zoom-select) {
1278
+ /* A native select draws the same chevron as the trigger's icon: Lucide's ChevronDown; see LICENSE. */
1279
+ :where(select.ink-select) {
886
1280
  padding-right: 2rem;
887
1281
  appearance: none;
888
1282
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235a6170' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
@@ -891,6 +1285,12 @@
891
1285
  background-size: 1rem;
892
1286
  }
893
1287
 
1288
+ /* The zoom select keeps one width as its value changes. */
1289
+ :where(.ink-zoom-select) {
1290
+ width: 7.5rem;
1291
+ font-variant-numeric: tabular-nums;
1292
+ }
1293
+
894
1294
  :where(.ink-select-value) {
895
1295
  overflow: hidden;
896
1296
  text-overflow: ellipsis;
@@ -1611,7 +2011,7 @@
1611
2011
  font-size: 0.875rem;
1612
2012
  }
1613
2013
 
1614
- :where(.ink-card-title, .ink-card-meta, .ink-card-body) {
2014
+ :where(.ink-card-title, .ink-card-meta, .ink-card-body, .ink-card-excerpt) {
1615
2015
  margin: 0;
1616
2016
  }
1617
2017
 
@@ -1640,6 +2040,17 @@
1640
2040
  color: var(--ink-muted-fg);
1641
2041
  }
1642
2042
 
2043
+ :where(.ink-card-excerpt) {
2044
+ margin-top: 0.25rem;
2045
+ color: var(--ink-muted-fg);
2046
+ }
2047
+
2048
+ :where(.ink-card-excerpt mark) {
2049
+ background: var(--ink-search-hit);
2050
+ /* The hit colour is light in both themes, so the text on it stays dark. */
2051
+ color: var(--ink-free-text);
2052
+ }
2053
+
1643
2054
  :where(.ink-card-actions) {
1644
2055
  display: flex;
1645
2056
  gap: 0.375rem;
@@ -1663,6 +2074,269 @@
1663
2074
  color: var(--ink-accent-fg);
1664
2075
  }
1665
2076
 
2077
+ /*
2078
+ * Record lists (createRecordList, createSearchResults and the bindings' lists): the same
2079
+ * cards as the popover, in a list that fills its parent's height and scrolls. These follow the
2080
+ * card rules so a row's card drops the popover's float, width and shadow.
2081
+ */
2082
+ :where(.ink-records) {
2083
+ display: flex;
2084
+ flex-direction: column;
2085
+ box-sizing: border-box;
2086
+ height: 100%;
2087
+ min-height: 0;
2088
+ }
2089
+
2090
+ :where(.ink-records:focus-visible) {
2091
+ outline: 2px solid var(--ink-ring);
2092
+ outline-offset: -2px;
2093
+ }
2094
+
2095
+ :where(.ink-record-list) {
2096
+ display: flex;
2097
+ flex: 1;
2098
+ flex-direction: column;
2099
+ gap: 0.5rem;
2100
+ min-height: 0;
2101
+ margin: 0;
2102
+ padding: 0.5rem;
2103
+ overflow-x: hidden;
2104
+ overflow-y: auto;
2105
+ list-style: none;
2106
+ }
2107
+
2108
+ :where(.ink-record-list[hidden], .ink-record-list-empty[hidden]) {
2109
+ display: none;
2110
+ }
2111
+
2112
+ :where(.ink-record) {
2113
+ border-radius: var(--ink-radius);
2114
+ cursor: pointer;
2115
+ }
2116
+
2117
+ :where(.ink-record > .ink-card) {
2118
+ z-index: auto;
2119
+ width: auto;
2120
+ max-width: none;
2121
+ box-shadow: none;
2122
+ }
2123
+
2124
+ :where(.ink-record:hover > .ink-card) {
2125
+ background: color-mix(in srgb, var(--ink-fg) 4%, var(--ink-surface));
2126
+ }
2127
+
2128
+ :where(.ink-record:focus-visible) {
2129
+ outline: 2px solid var(--ink-ring);
2130
+ outline-offset: 2px;
2131
+ }
2132
+
2133
+ :where(.ink-record[aria-current='true'] > .ink-card) {
2134
+ border-color: var(--ink-accent);
2135
+ box-shadow: inset 0 0 0 1px var(--ink-accent);
2136
+ }
2137
+
2138
+ :where(.ink-record .ink-card-actions:empty) {
2139
+ display: none;
2140
+ }
2141
+
2142
+ :where(.ink-record .ink-card-actions button) {
2143
+ min-height: 44px;
2144
+ }
2145
+
2146
+ :where(.ink-record-list-empty) {
2147
+ margin: 0;
2148
+ padding: 1rem 0.75rem;
2149
+ color: var(--ink-muted-fg);
2150
+ font-size: 0.875rem;
2151
+ }
2152
+
2153
+ /*
2154
+ * Search results frame their cards as the viewer frames its pages: the field on the surface
2155
+ * at the top, the cards on the page canvas, and the count in a footer on the surface at the
2156
+ * bottom, shown only when it has something to say.
2157
+ */
2158
+ :where(.ink-search-results) {
2159
+ display: flex;
2160
+ flex-direction: column;
2161
+ box-sizing: border-box;
2162
+ height: 100%;
2163
+ min-height: 0;
2164
+ background: var(--ink-bg);
2165
+ }
2166
+
2167
+ :where(.ink-search-results-bar) {
2168
+ display: flex;
2169
+ flex: none;
2170
+ align-items: center;
2171
+ padding: 0.5rem;
2172
+ border-bottom: 1px solid var(--ink-border);
2173
+ background: var(--ink-surface);
2174
+ }
2175
+
2176
+ :where(.ink-search-results-count) {
2177
+ flex: none;
2178
+ min-width: 0;
2179
+ padding: 0.5rem 0.75rem;
2180
+ border-top: 1px solid var(--ink-border);
2181
+ background: var(--ink-surface);
2182
+ color: var(--ink-muted-fg);
2183
+ font-size: 0.75rem;
2184
+ font-variant-numeric: tabular-nums;
2185
+ line-height: 1rem;
2186
+ }
2187
+
2188
+ :where(.ink-search-results-count:empty) {
2189
+ display: none;
2190
+ }
2191
+
2192
+ :where(.ink-search-results-bar input) {
2193
+ box-sizing: border-box;
2194
+ flex: 1;
2195
+ min-width: 0;
2196
+ min-height: 44px;
2197
+ padding: 0 0.5rem;
2198
+ border: 1px solid var(--ink-border);
2199
+ border-radius: calc(var(--ink-radius) - 2px);
2200
+ background: var(--ink-surface);
2201
+ color: inherit;
2202
+ font: inherit;
2203
+ }
2204
+
2205
+ :where(.ink-search-results-bar input:focus-visible) {
2206
+ outline: 2px solid var(--ink-ring);
2207
+ outline-offset: 2px;
2208
+ }
2209
+
2210
+ /*
2211
+ * An input with an accessory icon: wrap the input in `.ink-input-group` with an
2212
+ * `.ink-input-accessory` before it (at the start) or after it (at the end). The icon is
2213
+ * decorative and lets clicks through to the field. The group holds only the input and its
2214
+ * accessories, so an input that is not first or last has one beside it (no :has(), which
2215
+ * costs frames on long documents). After the find bar's and search results' input rules, so
2216
+ * its padding wins over theirs.
2217
+ */
2218
+ :where(.ink-input-group) {
2219
+ position: relative;
2220
+ display: flex;
2221
+ align-items: center;
2222
+ min-width: 0;
2223
+ }
2224
+
2225
+ :where(.ink-input-group > input) {
2226
+ flex: 1;
2227
+ min-width: 0;
2228
+ }
2229
+
2230
+ :where(.ink-input-accessory) {
2231
+ position: absolute;
2232
+ display: inline-flex;
2233
+ color: var(--ink-muted-fg);
2234
+ pointer-events: none;
2235
+ }
2236
+
2237
+ :where(.ink-input-accessory:first-child) {
2238
+ left: 0.625rem;
2239
+ }
2240
+
2241
+ :where(.ink-input-accessory:last-child) {
2242
+ right: 0.625rem;
2243
+ }
2244
+
2245
+ :where(.ink-input-accessory .ink-icon) {
2246
+ width: 1rem;
2247
+ height: 1rem;
2248
+ }
2249
+
2250
+ :where(.ink-input-group > input:not(:first-child)) {
2251
+ padding-left: 2rem;
2252
+ }
2253
+
2254
+ :where(.ink-input-group > input:not(:last-child)) {
2255
+ padding-right: 2rem;
2256
+ }
2257
+
2258
+ /* In the find bar and the search results, the group takes the field's place in the row. */
2259
+ :where(.ink-find-bar .ink-input-group, .ink-search-results-bar .ink-input-group) {
2260
+ flex: 1;
2261
+ }
2262
+
2263
+ :where(.ink-search-results > .ink-records) {
2264
+ flex: 1;
2265
+ height: auto;
2266
+ }
2267
+
2268
+ /*
2269
+ * Search result cards: the matched text leads, at full strength, and its page and place sit
2270
+ * in a small footer under a hairline. The cards are surfaces on the page canvas, as the
2271
+ * thumbnails are. The card's markup stays title, meta, excerpt; the grid reorders it.
2272
+ */
2273
+ :where(.ink-search-results .ink-record > .ink-card) {
2274
+ display: grid;
2275
+ grid-template-columns: auto 1fr;
2276
+ grid-template-areas:
2277
+ 'excerpt excerpt'
2278
+ 'title meta';
2279
+ padding: 0;
2280
+ overflow: hidden;
2281
+ border-color: color-mix(in srgb, var(--ink-fg) 10%, transparent);
2282
+ box-shadow: var(--ink-thumbnail-shadow);
2283
+ }
2284
+
2285
+ /* The ring sits outside: an inset one would go under the footer's background. */
2286
+ :where(.ink-search-results .ink-record[aria-current='true'] > .ink-card) {
2287
+ border-color: var(--ink-accent);
2288
+ box-shadow:
2289
+ 0 0 0 1px var(--ink-accent),
2290
+ var(--ink-thumbnail-shadow);
2291
+ }
2292
+
2293
+ :where(.ink-search-results .ink-card-excerpt) {
2294
+ display: -webkit-box;
2295
+ grid-area: excerpt;
2296
+ margin: 0;
2297
+ /* The space below is a border: a clamped box shows its next line through its padding, and
2298
+ an app's reset of p margins would win over the theme's layer. */
2299
+ padding: 0.625rem 0.75rem 0;
2300
+ border-bottom: 0.75rem solid transparent;
2301
+ overflow: hidden;
2302
+ color: var(--ink-fg);
2303
+ line-height: 1.45;
2304
+ -webkit-box-orient: vertical;
2305
+ -webkit-line-clamp: 3;
2306
+ line-clamp: 3;
2307
+ }
2308
+
2309
+ :where(.ink-search-results .ink-card-excerpt mark) {
2310
+ padding: 0 1px;
2311
+ border-radius: 2px;
2312
+ }
2313
+
2314
+ /* The current match's text takes the current hit's colour, as it does on the page. */
2315
+ :where(.ink-search-results .ink-record[aria-current='true'] .ink-card-excerpt mark) {
2316
+ background: var(--ink-search-current);
2317
+ }
2318
+
2319
+ :where(.ink-search-results .ink-card-title, .ink-search-results .ink-card-meta) {
2320
+ margin: 0;
2321
+ padding: 0.3125rem 0.75rem;
2322
+ border-top: 1px solid color-mix(in srgb, var(--ink-fg) 8%, transparent);
2323
+ background: color-mix(in srgb, var(--ink-fg) 3%, var(--ink-surface));
2324
+ font-size: 0.6875rem;
2325
+ line-height: 1rem;
2326
+ }
2327
+
2328
+ :where(.ink-search-results .ink-card-title) {
2329
+ grid-area: title;
2330
+ padding-right: 0;
2331
+ font-weight: 600;
2332
+ }
2333
+
2334
+ :where(.ink-search-results .ink-card-meta) {
2335
+ grid-area: meta;
2336
+ color: var(--ink-muted-fg);
2337
+ font-weight: 400;
2338
+ }
2339
+
1666
2340
  :where(.ink-select-item:focus-visible) {
1667
2341
  outline: none;
1668
2342
  }
@@ -1675,7 +2349,7 @@
1675
2349
  :where(
1676
2350
  .ink-toolbar button:focus-visible,
1677
2351
  .ink-toolbar select:focus-visible,
1678
- .ink-select-trigger:focus-visible,
2352
+ .ink-select:focus-visible,
1679
2353
  .ink-toolbar input:focus-visible,
1680
2354
  .ink-thread-actions button:focus-visible,
1681
2355
  .ink-thread-reply textarea:focus-visible,