@gjermundgaraba-designs/pixel-paper 0.22.2 → 0.23.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 (160) hide show
  1. package/DESIGN.md +13 -13
  2. package/dist/src/adapters/mermaid.d.ts.map +1 -1
  3. package/dist/src/adapters/mermaid.js +0 -24
  4. package/dist/src/announcer/announcer.d.ts.map +1 -1
  5. package/dist/src/announcer/announcer.js +12 -12
  6. package/dist/src/board/board.d.ts +0 -10
  7. package/dist/src/board/board.d.ts.map +1 -1
  8. package/dist/src/board/board.js +36 -64
  9. package/dist/src/board/index.d.ts +2 -1
  10. package/dist/src/board/index.d.ts.map +1 -1
  11. package/dist/src/board/index.js +2 -1
  12. package/dist/src/button/button.d.ts +1 -1
  13. package/dist/src/button/button.d.ts.map +1 -1
  14. package/dist/src/commandPalette/commandPalette.d.ts.map +1 -1
  15. package/dist/src/commandPalette/commandPalette.js +78 -83
  16. package/dist/src/commandPalette/keys.d.ts +2 -2
  17. package/dist/src/commandPalette/keys.d.ts.map +1 -1
  18. package/dist/src/commandPalette/keys.js +2 -4
  19. package/dist/src/commandPalette/match.d.ts.map +1 -1
  20. package/dist/src/commandPalette/match.js +2 -2
  21. package/dist/src/confirmDialog/confirmDialog.d.ts.map +1 -1
  22. package/dist/src/confirmDialog/confirmDialog.js +1 -2
  23. package/dist/src/copyButton/copyButton.d.ts +0 -5
  24. package/dist/src/copyButton/copyButton.d.ts.map +1 -1
  25. package/dist/src/copyButton/copyButton.js +2 -2
  26. package/dist/src/crumbs/crumbs.d.ts +0 -2
  27. package/dist/src/crumbs/crumbs.d.ts.map +1 -1
  28. package/dist/src/crumbs/crumbs.js +2 -2
  29. package/dist/src/dialog/dialog.d.ts +2 -0
  30. package/dist/src/dialog/dialog.d.ts.map +1 -1
  31. package/dist/src/dialog/dialog.js +3 -2
  32. package/dist/src/drag/drag.d.ts +28 -0
  33. package/dist/src/drag/drag.d.ts.map +1 -1
  34. package/dist/src/drag/drag.js +39 -17
  35. package/dist/src/editableTitle/editableTitle.d.ts +4 -4
  36. package/dist/src/field/field.d.ts +7 -1
  37. package/dist/src/field/field.d.ts.map +1 -1
  38. package/dist/src/field/field.js +14 -6
  39. package/dist/src/fieldset/fieldset.d.ts.map +1 -1
  40. package/dist/src/fieldset/fieldset.js +2 -4
  41. package/dist/src/fileTree/fileTree.d.ts.map +1 -1
  42. package/dist/src/fileTree/fileTree.js +16 -16
  43. package/dist/src/inlineInput/inlineInput.d.ts +4 -0
  44. package/dist/src/inlineInput/inlineInput.d.ts.map +1 -1
  45. package/dist/src/inlineInput/inlineInput.js +9 -6
  46. package/dist/src/keyHelp/index.d.ts +1 -1
  47. package/dist/src/keyHelp/index.d.ts.map +1 -1
  48. package/dist/src/keyHelp/index.js +1 -1
  49. package/dist/src/keyHelp/keyHelp.d.ts +1 -3
  50. package/dist/src/keyHelp/keyHelp.d.ts.map +1 -1
  51. package/dist/src/keyHelp/keyHelp.js +3 -6
  52. package/dist/src/keyHint/keyLabel.d.ts +2 -0
  53. package/dist/src/keyHint/keyLabel.d.ts.map +1 -1
  54. package/dist/src/keyHint/keyLabel.js +3 -2
  55. package/dist/src/lineDiff/diff.d.ts +6 -3
  56. package/dist/src/lineDiff/diff.d.ts.map +1 -1
  57. package/dist/src/lineDiff/diff.js +40 -28
  58. package/dist/src/lineDiff/patch.d.ts.map +1 -1
  59. package/dist/src/lineDiff/patch.js +18 -13
  60. package/dist/src/linkState/linkState.d.ts.map +1 -1
  61. package/dist/src/linkState/linkState.js +1 -7
  62. package/dist/src/menu/slip.js +2 -2
  63. package/dist/src/meter/meter.d.ts.map +1 -1
  64. package/dist/src/meter/meter.js +22 -17
  65. package/dist/src/notice/notice.d.ts +2 -0
  66. package/dist/src/notice/notice.d.ts.map +1 -1
  67. package/dist/src/notice/notice.js +2 -1
  68. package/dist/src/passwordField/passwordField.d.ts.map +1 -1
  69. package/dist/src/passwordField/passwordField.js +3 -7
  70. package/dist/src/rail/rail.d.ts +17 -3
  71. package/dist/src/rail/rail.d.ts.map +1 -1
  72. package/dist/src/rail/rail.js +6 -6
  73. package/dist/src/reel/layout.d.ts +9 -5
  74. package/dist/src/reel/layout.d.ts.map +1 -1
  75. package/dist/src/reel/layout.js +11 -5
  76. package/dist/src/reel/reel.d.ts.map +1 -1
  77. package/dist/src/reel/reel.js +122 -68
  78. package/dist/src/scheme/scheme.d.ts +1 -1
  79. package/dist/src/scheme/scheme.d.ts.map +1 -1
  80. package/dist/src/segmented/segmented.d.ts +0 -3
  81. package/dist/src/segmented/segmented.d.ts.map +1 -1
  82. package/dist/src/segmented/segmented.js +1 -6
  83. package/dist/src/selectField/selectField.d.ts.map +1 -1
  84. package/dist/src/selectField/selectField.js +3 -10
  85. package/dist/src/sortable/index.d.ts +1 -1
  86. package/dist/src/sortable/index.d.ts.map +1 -1
  87. package/dist/src/sortable/index.js +1 -1
  88. package/dist/src/sortable/sortable.d.ts +39 -40
  89. package/dist/src/sortable/sortable.d.ts.map +1 -1
  90. package/dist/src/sortable/sortable.js +14 -31
  91. package/dist/src/stack/stack.d.ts +1 -1
  92. package/dist/src/stack/stack.d.ts.map +1 -1
  93. package/dist/src/stack/stack.js +1 -1
  94. package/dist/src/tabs/tabs.d.ts +1 -1
  95. package/dist/src/tabs/tabs.d.ts.map +1 -1
  96. package/dist/src/tabs/tabs.js +1 -1
  97. package/dist/src/textareaField/textareaField.d.ts.map +1 -1
  98. package/dist/src/textareaField/textareaField.js +3 -7
  99. package/dist/src/time/time.d.ts.map +1 -1
  100. package/dist/src/time/time.js +4 -2
  101. package/dist/src/tips/index.d.ts +1 -1
  102. package/dist/src/tips/index.d.ts.map +1 -1
  103. package/dist/src/toast/index.d.ts +1 -1
  104. package/dist/src/toast/index.d.ts.map +1 -1
  105. package/dist/src/toast/toast.d.ts +5 -5
  106. package/dist/src/toast/toast.d.ts.map +1 -1
  107. package/dist/src/toast/toast.js +1 -6
  108. package/dist/src/toolbar/toolbar.d.ts +0 -3
  109. package/dist/src/toolbar/toolbar.d.ts.map +1 -1
  110. package/dist/src/toolbar/toolbar.js +1 -2
  111. package/package.json +1 -1
  112. package/src/adapters/mermaid.ts +0 -24
  113. package/src/announcer/announcer.ts +13 -17
  114. package/src/board/board.ts +32 -80
  115. package/src/board/index.ts +1 -1
  116. package/src/button/button.ts +1 -1
  117. package/src/commandPalette/commandPalette.ts +21 -38
  118. package/src/commandPalette/keys.ts +2 -4
  119. package/src/commandPalette/match.ts +3 -3
  120. package/src/confirmDialog/confirmDialog.ts +5 -3
  121. package/src/copyButton/copyButton.ts +2 -5
  122. package/src/crumbs/crumbs.ts +2 -4
  123. package/src/dialog/dialog.ts +3 -2
  124. package/src/drag/drag.ts +54 -5
  125. package/src/field/field.ts +29 -7
  126. package/src/fieldset/fieldset.ts +2 -4
  127. package/src/fileTree/fileTree.ts +10 -12
  128. package/src/inlineInput/inlineInput.ts +11 -2
  129. package/src/keyHelp/index.ts +0 -1
  130. package/src/keyHelp/keyHelp.ts +3 -9
  131. package/src/keyHint/keyLabel.ts +5 -2
  132. package/src/lineDiff/diff.ts +70 -54
  133. package/src/lineDiff/patch.ts +27 -14
  134. package/src/linkState/linkState.ts +1 -8
  135. package/src/menu/slip.ts +2 -2
  136. package/src/meter/meter.ts +25 -15
  137. package/src/notice/notice.ts +2 -1
  138. package/src/passwordField/passwordField.ts +3 -7
  139. package/src/pixel-paper.css +82 -131
  140. package/src/rail/rail.ts +8 -6
  141. package/src/reel/layout.ts +14 -7
  142. package/src/reel/reel.ts +132 -63
  143. package/src/scheme/scheme.ts +1 -1
  144. package/src/segmented/segmented.ts +1 -9
  145. package/src/selectField/selectField.ts +3 -10
  146. package/src/sortable/index.ts +0 -1
  147. package/src/sortable/sortable.ts +35 -54
  148. package/src/stack/stack.ts +1 -1
  149. package/src/tabs/tabs.ts +1 -1
  150. package/src/textareaField/textareaField.ts +3 -7
  151. package/src/time/time.ts +6 -7
  152. package/src/tips/index.ts +1 -1
  153. package/src/toast/index.ts +1 -1
  154. package/src/toast/toast.ts +2 -8
  155. package/src/tokens.css +6 -0
  156. package/src/toolbar/toolbar.ts +0 -5
  157. package/dist/src/inlineInput/inline.d.ts +0 -6
  158. package/dist/src/inlineInput/inline.d.ts.map +0 -1
  159. package/dist/src/inlineInput/inline.js +0 -7
  160. package/src/inlineInput/inline.ts +0 -13
@@ -105,32 +105,41 @@ const numbersText = (value: number, max: number, unit: string | undefined): stri
105
105
  return unit === undefined ? numbers : `${numbers} ${unit}`;
106
106
  };
107
107
 
108
+ /** How high a reading is: full at `max`, high from `HIGH_SHARE` of it, calm below. The
109
+ * percentage and a toned meter's cells both print it. */
110
+ type Level = "Full" | "High" | "Calm";
111
+
112
+ const levelOf = (value: number, max: number): Level => {
113
+ if (value >= max) {
114
+ return "Full";
115
+ } else if (value >= max * HIGH_SHARE) {
116
+ return "High";
117
+ } else {
118
+ return "Calm";
119
+ }
120
+ };
121
+
108
122
  const percentView = <Message>(
109
123
  config: MeterConfig,
110
124
  percent: number,
111
- isFull: boolean,
125
+ level: Level,
112
126
  h: HtmlBuilder<Message>,
113
127
  ): Html => {
114
128
  const percentText = `${percent}%`;
115
- const high = config.max * HIGH_SHARE;
116
129
 
117
- if (isFull) {
130
+ if (level === "Full") {
118
131
  return Chip.view({ label: config.full ?? percentText, tone: "Blocked" }, h);
119
- } else if (config.value >= high) {
132
+ } else if (level === "High") {
120
133
  return Mark.readingView({ label: percentText }, h);
121
134
  } else {
122
135
  return h.span([h.Class("pct")], [percentText]);
123
136
  }
124
137
  };
125
138
 
126
- const toneClass = (value: number, max: number, isFull: boolean): string => {
127
- if (isFull) {
128
- return " toned full";
129
- } else if (value >= max * HIGH_SHARE) {
130
- return " toned high";
131
- } else {
132
- return " toned";
133
- }
139
+ const TONE_CLASS: Readonly<Record<Level, string>> = {
140
+ Full: " toned full",
141
+ High: " toned high",
142
+ Calm: " toned",
134
143
  };
135
144
 
136
145
  const meterView = <Message>(config: MeterConfig, h: HtmlBuilder<Message>): Html => {
@@ -146,9 +155,10 @@ const meterView = <Message>(config: MeterConfig, h: HtmlBuilder<Message>): Html
146
155
  isToned = false,
147
156
  } = config;
148
157
  const share = max > 0 ? Math.min(1, Math.max(0, value / max)) : 0;
149
- const isFull = value >= max;
158
+ const level = levelOf(value, max);
159
+ const isFull = level === "Full";
150
160
  const numbers = config.valueText ?? numbersText(value, max, config.unit);
151
- const rowClass = `meter-row${isStale ? " stale" : ""}${isToned ? toneClass(value, max, isFull) : ""}`;
161
+ const rowClass = `meter-row${isStale ? " stale" : ""}${isToned ? TONE_CLASS[level] : ""}`;
152
162
 
153
163
  return UiMeter.view(
154
164
  {
@@ -179,7 +189,7 @@ const meterView = <Message>(config: MeterConfig, h: HtmlBuilder<Message>): Html
179
189
  [...groups.meter, h.Class("cells")],
180
190
  cellRowView(METER_CELLS, scaled(share, METER_CELLS, isFull), h),
181
191
  ),
182
- percentView(config, scaled(share, PERCENT, isFull), isFull, h),
192
+ percentView(config, scaled(share, PERCENT, isFull), level, h),
183
193
  ...(hasNumbers ? [h.span([h.Class("meter-num tnum")], [numbers])] : []),
184
194
  ],
185
195
  ),
@@ -22,7 +22,8 @@ const toneGlyph = (tone: Tone): GlyphName =>
22
22
  Match.exhaustive,
23
23
  );
24
24
 
25
- const TONE_CLASS: Readonly<Record<Tone, string>> = {
25
+ /** The class a tone adds to a notice's, a toast's or a glyph box's own. */
26
+ export const TONE_CLASS: Readonly<Record<Tone, string>> = {
26
27
  Warn: "",
27
28
  Error: " error",
28
29
  Ok: " ok",
@@ -3,7 +3,7 @@ import { Match, Schema } from "effect";
3
3
  import type { Html, HtmlBuilder } from "foldkit/html";
4
4
 
5
5
  import { pressedAttribute } from "../button/button.ts";
6
- import { describedByError, errorView } from "../field/field.ts";
6
+ import { controlAttributes, errorView, helpView } from "../field/field.ts";
7
7
  import * as IconButton from "../iconButton/iconButton.ts";
8
8
  import * as Meter from "../meter/meter.ts";
9
9
 
@@ -95,12 +95,10 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
95
95
  h.span(
96
96
  [h.Class("password")],
97
97
  [
98
- // NOTE: @foldkit/ui gap: Input has no required option, so the wrapper says it.
99
98
  h.input([
100
99
  ...groups.input,
101
100
  h.Autocomplete(autocomplete),
102
- ...describedByError(id, error, help !== undefined, h),
103
- ...(isRequired ? [h.AriaRequired(true)] : []),
101
+ ...controlAttributes(id, error, help, isRequired, h),
104
102
  ]),
105
103
  IconButton.view(
106
104
  {
@@ -136,9 +134,7 @@ export const view = <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Messag
136
134
  ],
137
135
  ),
138
136
  ]),
139
- ...(help === undefined
140
- ? []
141
- : [h.p([...groups.description, h.Class("field-help")], [help])]),
137
+ ...helpView(help, groups.description, h),
142
138
  ],
143
139
  ),
144
140
  },
@@ -13,11 +13,17 @@
13
13
 
14
14
  /* anchors land 16px clear of the sticky top bar, at the height TopBar measures it to be
15
15
  however it wraps; without Foldkit, at one line's height. A status bar along the bottom is
16
- kept clear the same way: the page ends above it, and so does a scrolled-to anchor */
16
+ kept clear the same way: the page ends above it, and so does a scrolled-to anchor. The
17
+ measured heights are written inline on <html>, over these */
18
+ :root {
19
+ --topbar-h: 48px;
20
+ --statusbar-h: 0px;
21
+ }
22
+
17
23
  html {
18
24
  height: 100%;
19
- scroll-padding-top: calc(var(--topbar-h, 48px) + 16px);
20
- scroll-padding-bottom: var(--statusbar-h, 0px);
25
+ scroll-padding-top: calc(var(--topbar-h) + 16px);
26
+ scroll-padding-bottom: var(--statusbar-h);
21
27
  }
22
28
 
23
29
  :root:has(footer.statusbar) {
@@ -26,7 +32,7 @@ html {
26
32
 
27
33
  body {
28
34
  min-height: 100%;
29
- padding-bottom: var(--statusbar-h, 0px);
35
+ padding-bottom: var(--statusbar-h);
30
36
  font-family: var(--font-body);
31
37
  font-size: var(--type-body);
32
38
  line-height: 1.45;
@@ -40,10 +46,6 @@ fieldset {
40
46
  min-width: 0;
41
47
  }
42
48
 
43
- legend {
44
- padding: 0;
45
- }
46
-
47
49
  ::selection {
48
50
  background: var(--ink);
49
51
  color: var(--paper);
@@ -93,7 +95,6 @@ kbd {
93
95
  position: absolute;
94
96
  width: 1px;
95
97
  height: 1px;
96
- padding: 0;
97
98
  margin: -1px;
98
99
  overflow: hidden;
99
100
  clip-path: inset(50%);
@@ -129,8 +130,8 @@ kbd {
129
130
  keeps its look and takes the pointer over an invisible 24px square centred on it */
130
131
  :is(
131
132
  .nav.tabs button,
132
- .switch > :is(a, button),
133
- :is(button, a, label).fchip,
133
+ .switch > button,
134
+ :is(button, label).fchip,
134
135
  button.glyph,
135
136
  button.mark,
136
137
  button.tag:has(> span),
@@ -143,8 +144,8 @@ kbd {
143
144
 
144
145
  :is(
145
146
  .nav.tabs button,
146
- .switch > :is(a, button),
147
- :is(button, a, label).fchip,
147
+ .switch > button,
148
+ :is(button, label).fchip,
148
149
  button.glyph,
149
150
  button.mark,
150
151
  button.tag:has(> span),
@@ -340,7 +341,6 @@ button.tag > span {
340
341
  font-size: var(--type-wordmark);
341
342
  font-weight: 400;
342
343
  letter-spacing: -0.02em;
343
- margin: 0;
344
344
  color: inherit;
345
345
  text-decoration: none;
346
346
  white-space: nowrap;
@@ -378,8 +378,8 @@ button.tag > span {
378
378
  flex: 1;
379
379
  }
380
380
 
381
- /* the right-hand tools wrap as one group, never one control per line */
382
- /* a bar of tools wider than the window wraps onto lines of its own, at the right */
381
+ /* the right-hand tools wrap as one group, never one control per line; a group wider than the
382
+ window wraps onto lines of its own, at the right */
383
383
  .topbar-tools {
384
384
  display: flex;
385
385
  flex-wrap: wrap;
@@ -456,7 +456,6 @@ a.textbtn {
456
456
  }
457
457
 
458
458
  /* tabs at the top of a flush panel sit inside its padding */
459
- .panel > .nav.tabs,
460
459
  .panel > .tabbed > .nav.tabs {
461
460
  margin: var(--pad) var(--pad) 0;
462
461
  }
@@ -498,7 +497,6 @@ a.textbtn {
498
497
  color: var(--on-head);
499
498
  }
500
499
 
501
- .nav.tabs a,
502
500
  .nav.tabs button {
503
501
  padding: 4px 12px;
504
502
  font-size: var(--type-state);
@@ -607,7 +605,7 @@ a.textbtn {
607
605
 
608
606
  .places li:has(> [aria-current]) :is(.place-count, .iconbtn.quiet) {
609
607
  color: inherit;
610
- opacity: 0.75;
608
+ opacity: var(--fade-on-ink);
611
609
  }
612
610
 
613
611
  .places li:has(> [aria-current]) :focus-visible {
@@ -798,8 +796,6 @@ a.textbtn {
798
796
  .menu {
799
797
  position: absolute;
800
798
  z-index: 200;
801
- margin: 0;
802
- padding: 0;
803
799
  min-width: 148px;
804
800
  max-width: calc(100vw - 24px);
805
801
  color: var(--ink);
@@ -875,7 +871,7 @@ a.textbtn {
875
871
 
876
872
  .menu :is([aria-current="page"], [role="menuitemradio"][aria-checked="true"]) .key-hint {
877
873
  color: inherit;
878
- opacity: 0.75;
874
+ opacity: var(--fade-on-ink);
879
875
  }
880
876
 
881
877
  .menu .menu-check {
@@ -952,7 +948,7 @@ a.textbtn {
952
948
  margin-left: auto;
953
949
  font-size: var(--type-state);
954
950
  color: inherit;
955
- opacity: 0.75;
951
+ opacity: var(--fade-on-ink);
956
952
  }
957
953
 
958
954
  .panel-body {
@@ -1046,14 +1042,12 @@ a.textbtn {
1046
1042
  color: var(--paper);
1047
1043
  background: var(--ink);
1048
1044
  border-color: var(--paper);
1049
- opacity: 0.6;
1045
+ opacity: var(--fade-held);
1050
1046
  }
1051
1047
 
1052
1048
  /* a quiet icon button prints in paper, and a hover cannot darken it to the ink of the bar */
1053
1049
  .panel-lead .iconbtn.quiet,
1054
- .panel-acts .iconbtn.quiet,
1055
- .panel-lead .iconbtn.quiet:hover,
1056
- .panel-acts .iconbtn.quiet:hover {
1050
+ .panel-acts .iconbtn.quiet {
1057
1051
  color: inherit;
1058
1052
  }
1059
1053
 
@@ -1087,7 +1081,6 @@ a.textbtn {
1087
1081
  flex: none;
1088
1082
  }
1089
1083
 
1090
- .switch a,
1091
1084
  .switch button {
1092
1085
  width: 26px;
1093
1086
  height: 22px;
@@ -1100,12 +1093,11 @@ a.textbtn {
1100
1093
  cursor: pointer;
1101
1094
  }
1102
1095
 
1103
- .switch > :is(a, button) + :is(a, button) {
1096
+ .switch > button + button {
1104
1097
  border-left: var(--line) solid var(--ink);
1105
1098
  }
1106
1099
 
1107
- .switch [aria-pressed="true"],
1108
- .switch [aria-current="page"] {
1100
+ .switch [aria-pressed="true"] {
1109
1101
  background: var(--ink);
1110
1102
  color: var(--paper);
1111
1103
  }
@@ -1159,11 +1151,6 @@ button.fchip:active {
1159
1151
  color: inherit;
1160
1152
  }
1161
1153
 
1162
- a.fchip {
1163
- cursor: pointer;
1164
- text-decoration: none;
1165
- }
1166
-
1167
1154
  .fchip.query {
1168
1155
  cursor: text;
1169
1156
  isolation: isolate;
@@ -1407,12 +1394,6 @@ button.glyph {
1407
1394
  margin: -4px;
1408
1395
  }
1409
1396
 
1410
- /* an inline edit in a row shows its focus as its caret and its rule, thickened below, not a
1411
- ring */
1412
- .row .grow input.inline-input:focus-visible {
1413
- outline: none;
1414
- }
1415
-
1416
1397
  /* the title opens the record; the pencil renames */
1417
1398
  .row .title {
1418
1399
  font: inherit;
@@ -1510,10 +1491,13 @@ button.glyph {
1510
1491
  outline-offset: -4px;
1511
1492
  }
1512
1493
 
1513
- /* the row you are on (its link is aria-current) prints inverted, as a place and a board's
1514
- cursor card do: hover adds nothing, a dot's border takes the paper, what prints muted on
1515
- paper prints in paper at 0.75, and lateness takes a red slip */
1516
- .row.linked:has(.grow a[aria-current]) {
1494
+ /* the row you are on (its link is aria-current) and a board's cursor card print inverted, as
1495
+ a place does and as the system marks the one current thing: hover adds nothing, what prints
1496
+ muted on paper prints in paper at --fade-on-ink, as a title bar's meta does, chips, marks
1497
+ and buttons keep their own fills, and a ring inside prints in paper. Each selector is
1498
+ written out, not joined in :is(), so each keeps its own specificity */
1499
+ .row.linked:has(.grow a[aria-current]),
1500
+ .board-card[aria-current="true"] {
1517
1501
  background-color: var(--ink);
1518
1502
  color: var(--paper);
1519
1503
  }
@@ -1522,16 +1506,20 @@ button.glyph {
1522
1506
  border-color: var(--paper);
1523
1507
  }
1524
1508
 
1525
- .row.linked:has(.grow a[aria-current]) :is(.glyph, .age, .due, .tag, .done-title, .iconbtn.quiet) {
1509
+ .row.linked:has(.grow a[aria-current]) :is(.glyph, .age, .due, .tag, .done-title, .iconbtn.quiet),
1510
+ .board-card[aria-current="true"] :is(.glyph, .age, .due, .tag, .done-title, .iconbtn.quiet) {
1526
1511
  color: inherit;
1527
- opacity: 0.75;
1512
+ opacity: var(--fade-on-ink);
1528
1513
  }
1529
1514
 
1530
- .row.linked:has(.grow a[aria-current]) .tag {
1515
+ .row.linked:has(.grow a[aria-current]) .tag,
1516
+ .board-card[aria-current="true"] .tag {
1531
1517
  border-color: currentColor;
1532
1518
  }
1533
1519
 
1534
- .row.linked:has(.grow a[aria-current]) :is(.due.late, .age.stale) {
1520
+ /* lateness cannot be red on ink: it takes a red slip, drawn by a spread so nothing moves */
1521
+ .row.linked:has(.grow a[aria-current]) :is(.due.late, .age.stale),
1522
+ .board-card[aria-current="true"] :is(.due.late, .age.stale) {
1535
1523
  opacity: 1;
1536
1524
  background: var(--red);
1537
1525
  color: var(--on-red);
@@ -1539,7 +1527,8 @@ button.glyph {
1539
1527
  }
1540
1528
 
1541
1529
  .row.linked:has(.grow a[aria-current]) :focus-visible,
1542
- .row.linked:has(.grow a[aria-current]:focus-visible) {
1530
+ .row.linked:has(.grow a[aria-current]:focus-visible),
1531
+ .board-card[aria-current="true"] :focus-visible {
1543
1532
  outline-color: var(--paper);
1544
1533
  }
1545
1534
 
@@ -1566,35 +1555,6 @@ button.glyph {
1566
1555
  opacity: 1;
1567
1556
  }
1568
1557
 
1569
- /* a board's cursor card is inverted, as the system marks the one current thing. What prints
1570
- muted on paper prints in paper at 0.75, as a title bar's meta does; chips, marks and buttons
1571
- keep their own fills; a ring inside it prints in paper. Hover adds nothing to it */
1572
- .board-card[aria-current="true"] {
1573
- background-color: var(--ink);
1574
- color: var(--paper);
1575
- }
1576
-
1577
- .board-card[aria-current="true"] :is(.glyph, .age, .due, .tag, .done-title, .iconbtn.quiet) {
1578
- color: inherit;
1579
- opacity: 0.75;
1580
- }
1581
-
1582
- .board-card[aria-current="true"] .tag {
1583
- border-color: currentColor;
1584
- }
1585
-
1586
- /* lateness cannot be red on ink: it takes a red slip, drawn by a spread so nothing moves */
1587
- .board-card[aria-current="true"] :is(.due.late, .age.stale) {
1588
- opacity: 1;
1589
- background: var(--red);
1590
- color: var(--on-red);
1591
- box-shadow: 0 0 0 3px var(--red);
1592
- }
1593
-
1594
- .board-card[aria-current="true"] :focus-visible {
1595
- outline-color: var(--paper);
1596
- }
1597
-
1598
1558
  /* a phone has no room for the hover tools; the detail card carries every edit. Titles
1599
1559
  wrap to a second line instead of clipping to a few characters beside the chips */
1600
1560
  @media (max-width: 600px) {
@@ -1747,10 +1707,11 @@ input.inline-input[aria-invalid="true"] {
1747
1707
  border-bottom-color: var(--red);
1748
1708
  }
1749
1709
 
1750
- /* in a row, which takes its ring, focus doubles the rule to 2px, as a field's baseline does;
1751
- drawn inside the box, so the row keeps its height, and in the rule's own colour, so a
1752
- refused edit stays red */
1710
+ /* in a row, focus shows as the caret and the rule, not a ring: the rule doubles to 2px, as a
1711
+ field's baseline does, drawn inside the box, so the row keeps its height, and in the rule's
1712
+ own colour, so a refused edit stays red */
1753
1713
  .row .grow input.inline-input:focus-visible {
1714
+ outline: none;
1754
1715
  box-shadow: inset 0 -1px 0 var(--ink);
1755
1716
  }
1756
1717
 
@@ -2081,7 +2042,6 @@ textarea::-webkit-resizer {
2081
2042
  font-size: var(--type-control);
2082
2043
  }
2083
2044
 
2084
- .title-edit,
2085
2045
  input.inline-input.title-edit {
2086
2046
  font-size: var(--type-title);
2087
2047
  font-weight: 600;
@@ -2292,12 +2252,6 @@ select[aria-invalid="true"] {
2292
2252
  margin-left: 8px;
2293
2253
  }
2294
2254
 
2295
- .table .chips {
2296
- display: inline-flex;
2297
- gap: 6px;
2298
- flex-wrap: wrap;
2299
- }
2300
-
2301
2255
  /* a stacked table: on a phone each row becomes a block of label and value lines, so a
2302
2256
  pinned action column never slices through what the row says */
2303
2257
  @media (max-width: 600px) {
@@ -2719,15 +2673,10 @@ pre.code-surface {
2719
2673
  element of its own (.veil-backdrop) under the pad; the pad lets a press through to it
2720
2674
  everywhere but on the card it holds, and a press on the veil closes the dialog. */
2721
2675
  dialog.veil {
2722
- margin: 0;
2723
2676
  color: inherit;
2724
2677
  overflow-y: auto;
2725
2678
  }
2726
2679
 
2727
- dialog.veil[open] {
2728
- display: block;
2729
- }
2730
-
2731
2680
  dialog.veil:focus {
2732
2681
  outline: none;
2733
2682
  }
@@ -2819,12 +2768,12 @@ dialog.veil:focus {
2819
2768
  .card-tb .meta {
2820
2769
  font-size: var(--type-state);
2821
2770
  color: inherit;
2822
- opacity: 0.75;
2771
+ opacity: var(--fade-on-ink);
2823
2772
  }
2824
2773
 
2825
2774
  /* a held icon button fades on the bar, as a held button in a panel's title bar does */
2826
2775
  :is(.card-tb, .panel-tb) .iconbtn[aria-disabled="true"] {
2827
- opacity: 0.6;
2776
+ opacity: var(--fade-held);
2828
2777
  cursor: default;
2829
2778
  }
2830
2779
 
@@ -3066,7 +3015,7 @@ dialog.veil:focus {
3066
3015
  .palette-row[aria-selected="true"] .hint,
3067
3016
  .palette-row[aria-selected="true"] .tag {
3068
3017
  color: inherit;
3069
- opacity: 0.75;
3018
+ opacity: var(--fade-on-ink);
3070
3019
  }
3071
3020
 
3072
3021
  .palette-row[aria-selected="true"] .tag {
@@ -3130,7 +3079,6 @@ dialog.veil:focus {
3130
3079
  }
3131
3080
 
3132
3081
  .empty-title {
3133
- margin: 0;
3134
3082
  font-family: var(--font-px);
3135
3083
  font-size: var(--type-control);
3136
3084
  text-transform: uppercase;
@@ -3151,7 +3099,7 @@ dialog.veil:focus {
3151
3099
 
3152
3100
  .banner {
3153
3101
  position: fixed;
3154
- bottom: calc(var(--statusbar-h, 0px) + 16px);
3102
+ bottom: calc(var(--statusbar-h) + 16px);
3155
3103
  left: 50%;
3156
3104
  transform: translateX(-50%);
3157
3105
  z-index: 150;
@@ -3408,7 +3356,7 @@ dialog.veil:focus {
3408
3356
  }
3409
3357
 
3410
3358
  .board-card.drag-dim {
3411
- opacity: 0.4;
3359
+ opacity: var(--fade-dragged);
3412
3360
  }
3413
3361
 
3414
3362
  /* focus on the title rings the whole slip, outside its own border */
@@ -3501,7 +3449,7 @@ dialog.veil:focus {
3501
3449
  }
3502
3450
 
3503
3451
  .sortable-item.drag-dim {
3504
- opacity: 0.4;
3452
+ opacity: var(--fade-dragged);
3505
3453
  }
3506
3454
 
3507
3455
  /* where a pointer would drop the held item: a 2px dashed ink line beside the item it lands
@@ -5064,7 +5012,6 @@ dialog.veil:focus {
5064
5012
  display: flex;
5065
5013
  flex-wrap: wrap;
5066
5014
  gap: 4px 12px;
5067
- margin: 0;
5068
5015
  }
5069
5016
 
5070
5017
  .statusbar-keys .key-hint {
@@ -5076,24 +5023,34 @@ dialog.veil:focus {
5076
5023
 
5077
5024
  /* one element in two places: at rest, fixed down the left edge from the top bar's lower
5078
5025
  edge (as the stage hangs) and scrolling itself; on a phone the same element opens as a
5079
- popover drawer. --rail is the reader's width when they gave one: the view sets it on
5080
- .railed, so the page's padding and the rail's width follow it together */
5026
+ popover drawer. The reader's width, when they gave one, is --railed-width, which the view
5027
+ sets on .railed, so the page's padding and the rail's width follow it together; without one,
5028
+ --rail. It is not inherited: each box it sizes takes it from .railed by name, so a width that
5029
+ changes under a dragged splitter restyles those four and none of the rows inside them */
5030
+ @property --railed-width {
5031
+ syntax: "*";
5032
+ inherits: false;
5033
+ }
5034
+
5081
5035
  .railed {
5082
- padding-left: var(--rail);
5036
+ padding-left: var(--railed-width, var(--rail));
5037
+ }
5038
+
5039
+ .railed > :is(.rail, .rail-resize, .bench) {
5040
+ --railed-width: inherit;
5083
5041
  }
5084
5042
 
5085
5043
  .rail[popover] {
5086
5044
  position: fixed;
5087
5045
  inset: auto;
5088
5046
  top: anchor(--topbar bottom, 0px);
5089
- bottom: var(--statusbar-h, 0px);
5047
+ bottom: var(--statusbar-h);
5090
5048
  left: 0;
5091
5049
  display: grid;
5092
5050
  align-content: start;
5093
5051
  gap: var(--gap);
5094
- width: var(--rail);
5052
+ width: var(--railed-width, var(--rail));
5095
5053
  height: auto;
5096
- margin: 0;
5097
5054
  padding: var(--gap) 10px var(--gap) 20px;
5098
5055
  border: 0;
5099
5056
  background: none;
@@ -5112,8 +5069,8 @@ dialog.veil:focus {
5112
5069
  .rail-resize {
5113
5070
  position: fixed;
5114
5071
  top: anchor(--topbar bottom, 0px);
5115
- bottom: var(--statusbar-h, 0px);
5116
- left: var(--rail);
5072
+ bottom: var(--statusbar-h);
5073
+ left: var(--railed-width, var(--rail));
5117
5074
  z-index: 1;
5118
5075
  width: 12px;
5119
5076
  cursor: col-resize;
@@ -5196,21 +5153,24 @@ dialog.veil:focus {
5196
5153
  /* hung from the top bar's lower edge like the stage, down to the status bar, edge to edge, or
5197
5154
  from the rail's edge beside one (a fixed box takes nothing from .railed's padding). The head
5198
5155
  takes its own height while the work keeps at least --bench-work-min, or all but the head's
5199
- first 64px (its padding and a small button: the rail's open button) on a window too short
5156
+ first --bench-head-min (its padding and a small button: the rail's open button) on a window too short
5200
5157
  for both; past that the head scrolls itself. The work takes the rest and lays out what
5201
5158
  fills it (a reel's height is 100% of it). The page does not scroll */
5202
5159
  .bench {
5203
5160
  position: fixed;
5204
5161
  top: anchor(--topbar bottom, 0px);
5205
5162
  right: 0;
5206
- bottom: var(--statusbar-h, 0px);
5163
+ bottom: var(--statusbar-h);
5207
5164
  left: 0;
5208
5165
  display: grid;
5209
- grid-template-rows: minmax(0, max-content) minmax(min(var(--bench-work-min), 100% - 64px), 1fr);
5166
+ grid-template-rows: minmax(0, max-content) minmax(
5167
+ min(var(--bench-work-min), 100% - var(--bench-head-min)),
5168
+ 1fr
5169
+ );
5210
5170
  }
5211
5171
 
5212
5172
  .railed > .bench {
5213
- left: var(--rail);
5173
+ left: var(--railed-width, var(--rail));
5214
5174
  }
5215
5175
 
5216
5176
  /* the head lines up with a reel's windows, a reel's gutter in from the edge; the reel's own
@@ -5248,7 +5208,7 @@ dialog.veil:focus {
5248
5208
  position: fixed;
5249
5209
  top: anchor(--topbar bottom, 0px);
5250
5210
  right: 0;
5251
- bottom: var(--statusbar-h, 0px);
5211
+ bottom: var(--statusbar-h);
5252
5212
  left: 0;
5253
5213
  overflow: hidden;
5254
5214
  }
@@ -5347,8 +5307,7 @@ dialog.veil:focus {
5347
5307
  border-color: var(--paper);
5348
5308
  }
5349
5309
 
5350
- .window > .panel-tb .iconbtn,
5351
- .window > .panel-tb .iconbtn.quiet {
5310
+ .window > .panel-tb .iconbtn {
5352
5311
  color: inherit;
5353
5312
  }
5354
5313
 
@@ -5558,7 +5517,7 @@ dialog.veil:focus {
5558
5517
  }
5559
5518
 
5560
5519
  .reel-pane.drag-dim {
5561
- opacity: 0.4;
5520
+ opacity: var(--fade-dragged);
5562
5521
  }
5563
5522
 
5564
5523
  /* only the grips lift: a pane's text selects and its terminal takes the pointer */
@@ -5767,7 +5726,6 @@ dialog.veil:focus {
5767
5726
  inset: 0;
5768
5727
  width: 100%;
5769
5728
  height: 100%;
5770
- margin: 0;
5771
5729
  opacity: 0;
5772
5730
  appearance: none;
5773
5731
  cursor: pointer;
@@ -5826,7 +5784,7 @@ dialog.veil:focus {
5826
5784
  padding from the corner), so the stylesheet raises it clear of a banner, its anchor when one
5827
5785
  is showing, or of a status bar, and sets the gap. A phone spans it across the bottom */
5828
5786
  .toasts .toast-stack {
5829
- bottom: calc(anchor(--banner top, calc(var(--statusbar-h, 0px) + 4px)) - 4px) !important;
5787
+ bottom: calc(anchor(--banner top, calc(var(--statusbar-h) + 4px)) - 4px) !important;
5830
5788
  gap: 10px !important;
5831
5789
  width: min(392px, 100vw);
5832
5790
  }
@@ -5874,21 +5832,14 @@ dialog.veil:focus {
5874
5832
  }
5875
5833
  }
5876
5834
 
5877
- @media (prefers-reduced-motion: reduce) {
5878
- .toasts .toast {
5879
- animation: none;
5880
- }
5881
- }
5882
-
5883
5835
  /* ---------- reduced motion ---------- */
5884
5836
 
5885
5837
  @media (prefers-reduced-motion: reduce) {
5886
- .cursor {
5887
- animation: none;
5888
- }
5838
+ .cursor,
5889
5839
  .veil-backdrop,
5890
5840
  .card,
5891
- .rail[popover]:popover-open {
5841
+ .rail[popover]:popover-open,
5842
+ .toasts .toast {
5892
5843
  animation: none;
5893
5844
  }
5894
5845
  }