@quillmark/svelte 0.26.2 → 0.28.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 (97) hide show
  1. package/NOTICE +4 -4
  2. package/README.md +26 -8
  3. package/THEMING.md +40 -4
  4. package/dist/core/address.d.ts +35 -64
  5. package/dist/core/address.js +35 -55
  6. package/dist/core/bloom.d.ts +4 -10
  7. package/dist/core/bloom.js +14 -27
  8. package/dist/core/codec/blocks.d.ts +41 -43
  9. package/dist/core/codec/blocks.js +97 -130
  10. package/dist/core/codec/code.d.ts +7 -10
  11. package/dist/core/codec/code.js +37 -46
  12. package/dist/core/codec/decode.d.ts +19 -23
  13. package/dist/core/codec/decode.js +76 -111
  14. package/dist/core/codec/encode.d.ts +11 -15
  15. package/dist/core/codec/encode.js +60 -67
  16. package/dist/core/codec/field.d.ts +62 -95
  17. package/dist/core/codec/field.js +107 -179
  18. package/dist/core/codec/islands.d.ts +33 -41
  19. package/dist/core/codec/islands.js +76 -86
  20. package/dist/core/codec/lists.d.ts +17 -26
  21. package/dist/core/codec/lists.js +117 -157
  22. package/dist/core/codec/marks.d.ts +4 -3
  23. package/dist/core/codec/marks.js +8 -3
  24. package/dist/core/codec/prose.css +84 -46
  25. package/dist/core/codec/schema.d.ts +18 -23
  26. package/dist/core/codec/schema.js +84 -76
  27. package/dist/core/codec/slash.d.ts +42 -48
  28. package/dist/core/codec/slash.js +0 -0
  29. package/dist/core/codec/table-copy.d.ts +4 -3
  30. package/dist/core/codec/table-copy.js +25 -7
  31. package/dist/core/codec/table-view.d.ts +12 -12
  32. package/dist/core/codec/table-view.js +460 -383
  33. package/dist/core/codec/table.d.ts +94 -61
  34. package/dist/core/codec/table.js +331 -83
  35. package/dist/core/codec/tasks.d.ts +6 -0
  36. package/dist/core/codec/tasks.js +30 -3
  37. package/dist/core/errors.d.ts +15 -30
  38. package/dist/core/errors.js +2 -6
  39. package/dist/core/index.js +7 -12
  40. package/dist/core/lifecycle.d.ts +7 -16
  41. package/dist/core/lifecycle.js +10 -21
  42. package/dist/core/rebind.svelte.d.ts +10 -20
  43. package/dist/core/rebind.svelte.js +15 -27
  44. package/dist/core/rungs.d.ts +14 -11
  45. package/dist/core/rungs.js +17 -19
  46. package/dist/core/teardown.d.ts +4 -6
  47. package/dist/core/teardown.js +7 -10
  48. package/dist/core/theme.css +74 -54
  49. package/dist/index.js +3 -8
  50. package/dist/preset/app.css +71 -0
  51. package/dist/preset/index.css +8 -7
  52. package/dist/preset/recipes.css +19 -164
  53. package/dist/preset/scale.css +28 -18
  54. package/dist/preset/shell.css +157 -0
  55. package/dist/preview/Preview.svelte +8 -0
  56. package/dist/preview/bridge.d.ts +18 -22
  57. package/dist/preview/bridge.js +29 -58
  58. package/dist/preview/controller.d.ts +21 -29
  59. package/dist/preview/controller.js +14 -39
  60. package/dist/preview/geometry.d.ts +13 -34
  61. package/dist/preview/geometry.js +13 -34
  62. package/dist/preview/index.js +4 -16
  63. package/dist/preview/paint.d.ts +3 -3
  64. package/dist/preview/paint.js +28 -68
  65. package/dist/preview/strings.d.ts +1 -1
  66. package/dist/preview/strings.js +2 -14
  67. package/dist/preview/viewport.js +20 -15
  68. package/dist/preview/zoom.d.ts +21 -22
  69. package/dist/preview/zoom.js +53 -30
  70. package/dist/visual/ArrayField.svelte +77 -16
  71. package/dist/visual/BooleanField.svelte +28 -15
  72. package/dist/visual/Card.svelte +66 -51
  73. package/dist/visual/CardControls.svelte +5 -11
  74. package/dist/visual/DateField.svelte +20 -4
  75. package/dist/visual/EnumField.svelte +3 -0
  76. package/dist/visual/FieldHint.svelte +2 -1
  77. package/dist/visual/FormatPopover.svelte +82 -15
  78. package/dist/visual/FormatPopover.svelte.d.ts +4 -0
  79. package/dist/visual/MatrixAdd.svelte +30 -12
  80. package/dist/visual/MatrixField.svelte +24 -16
  81. package/dist/visual/NumberField.svelte +7 -0
  82. package/dist/visual/ObjectField.svelte +4 -4
  83. package/dist/visual/ProseField.svelte +0 -5
  84. package/dist/visual/TipsCard.svelte +11 -10
  85. package/dist/visual/VariantField.svelte +3 -1
  86. package/dist/visual/VisualEditorInner.svelte +19 -3
  87. package/dist/visual/controls.css +7 -2
  88. package/dist/visual/fonts.d.ts +18 -0
  89. package/dist/visual/fonts.js +59 -0
  90. package/dist/visual/icons/nodes.d.ts +0 -5
  91. package/dist/visual/icons/nodes.js +0 -4
  92. package/dist/visual/props.d.ts +14 -1
  93. package/dist/visual/strings.d.ts +7 -0
  94. package/dist/visual/strings.js +5 -2
  95. package/dist/visual/structure.d.ts +43 -87
  96. package/dist/visual/structure.js +42 -83
  97. package/package.json +2 -2
@@ -101,11 +101,10 @@
101
101
  --_qm-font-mono: var(--qm-font-mono, ui-monospace, monospace);
102
102
 
103
103
  /* ── Colour ──────────────────────────────────────────────────────────────
104
- Ink steps fg → bg, the border and hover rungs step bg → fg, all mixed in
105
- oklab; sRGB muddies the mid-tones and the label/meta/border rungs are
106
- exactly mid-tone. So inverting the poles inverts that half of the scale.
107
- The percentages are calibrated to the light-mode literals they stand in
108
- for (17% ≈ #d4d4d4, 35% ≈ #555).
104
+ Ink steps fg → bg and the hover rung steps bg → fg, both mixed in oklab;
105
+ sRGB muddies the mid-tones and the label rung is exactly mid-tone. So
106
+ inverting the poles inverts that half of the scale. The percentages are
107
+ calibrated to the light-mode literals they stand in for (35% ≈ #555).
109
108
 
110
109
  Planes are the half that does not read the ink pole, because what makes a
111
110
  card read as a card is that it is the brighter plane, in both schemes
@@ -115,57 +114,68 @@
115
114
  light, and the base plane — a card, the preview's paper — is the pole
116
115
  itself.
117
116
 
118
- Two planes sink. `sunken` is the ground — the column behind the cards, the
119
- desk behind the sheets — and lifting the plane above it takes very little
120
- at the light pole. `well` is the hole cut into a card: a field, the one
121
- native select, neither of which draws an edge, so the step is the whole of
122
- what says a value is typed there. The order at the
123
- light pole is card, ground, well; at the dark pole the two sink to within
124
- a channel of each other, which is where a card with little light left to
125
- give up leaves them.
117
+ One plane sinks, in two roles. `well` is the hole cut into a card: a field,
118
+ the one native select, neither of which draws an edge, so the step is the
119
+ whole of what says a value is typed there. `sunken` is the ground — the
120
+ column behind the cards, the desk behind the sheets — and it is the same
121
+ tone: a card draws no edge either, so the step off the ground is the whole
122
+ of where one card stops and the next starts, and the well's step is one
123
+ that carries that at the light pole. The order is card, then ground
124
+ and well together, at both poles.
126
125
 
127
- How far each sinks is a function of the card's own L, not of the scheme the
126
+ How far it sinks is a function of the card's own L, not of the scheme the
128
127
  host declared: a dark surface has to spend more of what little it has
129
128
  before the card above it reads as lifted, so the two calibrated distances
130
- — 0.0214 off white, 0.0553 off the dark default — interpolate linearly
129
+ — 0.04 off white, 0.0547 off the dark default — interpolate linearly
131
130
  between. A card pinned at neither pole takes a proportionate step whatever
132
131
  `color-scheme` says, which is the half of the derivation that reads a
133
132
  colour rather than a declaration.
134
133
 
135
- Both sink by subtraction where every other rung mixes: a mix toward black
134
+ It sinks by subtraction where every other rung mixes: a mix toward black
136
135
  is a ratio, so it scales what it takes and pulls the result toward
137
136
  neutral, and a ground is a fixed distance. `a b` pass through, so the
138
137
  plane keeps the pole's hue and a turned `--qm-bg` tints it. The defaults
139
- land within a channel of `#f7f8f9` and `#1a1a1b`. Below L ≈ 0.065 the
140
- ground clamps to black, which is where a near-black card leaves no room
138
+ land within a channel of `#f2f2f2` and `#1a1a1c`. Below L ≈ 0.06 the
139
+ plane clamps to black, which is where a near-black card leaves no room
141
140
  for a ground of its own. */
142
141
  --_qm-surface: var(--_qm-bg);
143
- --_qm-surface-sunken: oklab(from var(--_qm-bg) calc(l - (0.0214 + 0.0467 * (1 - l))) a b);
144
142
  --_qm-surface-well: oklab(from var(--_qm-bg) calc(l - (0.04 + 0.0202 * (1 - l))) a b);
143
+ --_qm-surface-sunken: var(--_qm-surface-well);
145
144
  /* `raised` is the same ramp spelled as a mix, its step 0.08 × (1 − l): the light
146
145
  arm is zero, a white card having nowhere further to go, so a floating surface
147
146
  separates on its hairline and `--_qm-lift` there and the step joins them in
148
147
  dark. */
149
148
  --_qm-surface-raised: color-mix(in oklab, var(--_qm-bg), #fff 8%);
150
- --_qm-surface-hover: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 8%);
151
- /* A band laid on the card rather than cut into it: a table's header, and the pill a
152
- grip draws. It steps toward the ink at both poles, a shade under white in light
153
- and a shade over the card in dark, where `well` sinks to a black stripe. */
154
- --_qm-surface-band: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 6%);
155
- /* The one rung whose arms are not a single ratio: a stroke is read against what it
156
- separates, and the mix that reads as a hairline on the dark default draws the
157
- light card in a line heavier than the type it frames. The light arm is
158
- calibrated rather than derived, which makes this the one rung under the poles
159
- that reads the declared scheme rather than only the colours it resolves to. */
149
+ /* A step toward the ink laid over whatever plane is under it, rather than a tone off
150
+ the card: a hover lands on the card, a well, a band and a floating surface, and a
151
+ fixed tone stepped off the first sits level with `raised` in dark. Translucent, as
152
+ the accent's tint is, so the step is the same over each. */
153
+ --_qm-surface-hover: color-mix(in oklab, var(--_qm-fg) 8%, transparent);
154
+ /* A band laid on the card rather than cut into it: a table's header, the pill a
155
+ grip draws, the off switch's track and the unticked box. A resting fill, where
156
+ `hover` above is a state, so the two have to be told apart on a card that shows
157
+ both at once: each arm stands one ground step past the hover step at its pole,
158
+ which lands the band two steps off the card in dark and a little over in light.
159
+ It steps toward the ink at both poles, a shade under white in light and a shade
160
+ over the card in dark, where `well` sinks to a black stripe. */
161
+ --_qm-surface-band: light-dark(
162
+ color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 12%),
163
+ color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 18%)
164
+ );
165
+ /* The one stroke: a card's two horizontals, a section's and a subform's verticals,
166
+ a table's frame and the lines between its cells, a floating surface's edge, the
167
+ popover's divider. One and a half ground steps off the card at both poles, the
168
+ same derivation the well takes, so a hairline stands off the card by the same
169
+ measure a card stands off the ground whatever the scheme. Toward the ink, which
170
+ is the half of the step `light-dark()` carries: the well always sinks, a stroke
171
+ darkens a light card and lightens a dark one. On the defaults the arms land near
172
+ #ececec and #3b3c3e. The multiple is the whole calibration: at it a stroke reads
173
+ in light without outweighing the type beside it, and reads in dark without being
174
+ the loudest line on the card. */
160
175
  --_qm-border: light-dark(
161
- color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 6%),
162
- color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 17%)
176
+ oklab(from var(--_qm-bg) calc(l - 1.5 * (0.04 + 0.0202 * (1 - l))) a b),
177
+ oklab(from var(--_qm-bg) calc(l + 1.5 * (0.04 + 0.0202 * (1 - l))) a b)
163
178
  );
164
- /* A stroke ruled through content rather than round it: a table's frame and every
165
- line between its cells. The light arm of `border` is a card's edge against the
166
- ground, and drawn between lines of type it vanishes, so a grid takes one ratio at
167
- both poles, which lands within a few points of `border` in dark. */
168
- --_qm-rule: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 14%);
169
179
  /* Three rungs, and nothing under the last: a step below `label` lands under 3:1 on
170
180
  the well a field draws it against, and what would take it — an unset value's word,
171
181
  an invitation — is content a reader has to read (WCAG 1.4.3), not decoration.
@@ -259,7 +269,7 @@
259
269
  only up to the point where it is assembled. One width: focus is the only
260
270
  state that draws a ring anywhere, so there is no held-back tier under it.
261
271
  Sub-pixel, because at two it reads as a second border on a card whose every
262
- other edge is a hairline. */
272
+ other stroke is a hairline. */
263
273
  --_qm-ring-width: 1.5px;
264
274
  /* Every ring the surface draws, focus and selection alike, which is why the name
265
275
  carries neither. Assembled here because the parts spelled out at a use site pass
@@ -296,17 +306,17 @@
296
306
 
297
307
  /* ── Geometry ──────────────────────────────────────────────────────────*/
298
308
  /* One stroke width for every edge the chrome draws and every rule it ranks
299
- with: a card's, a control's, the two closing a card's payload. A threshold
300
- rather than a rhythm choice, like the tap floor: a hairline is a hairline at
301
- any `--qm-space`, so neither derives from a dial. Both exist as rungs because
309
+ with: a control's, a floating surface's, the two closing a card's payload. A
310
+ threshold rather than a rhythm choice, like the tap floor: a hairline is a
311
+ hairline at any `--qm-space`, so neither derives from a dial. Both exist as rungs because
302
312
  `check:style`'s border-width axis can only hold a stroke against a token. */
303
313
  --_qm-border-width: 1px;
304
314
  /* Half a step over it, for the verticals alone — a section's, a subform's, a
305
315
  blockquote's. What a vertical states is depth, and depth is the one thing a
306
- reader has to find in a dense card; the strokes that close a block or rank two
307
- stay at the hairline, so the ladder is what carries the extra weight. Half,
308
- because a whole step reads as a different kind of line rather than the same
309
- one doing more work. */
316
+ reader has to find in a dense card; the strokes that rank two or edge a
317
+ floating surface stay at the hairline, so the ladder is what carries the extra
318
+ weight. Half, because a whole step reads as a different kind of line rather
319
+ than the same one doing more work. */
310
320
  --_qm-vertical-width: 1.5px;
311
321
  --_qm-radius: var(--qm-radius, 8px);
312
322
  /* The interior corner, and half is the relationship rather than the number: a
@@ -342,6 +352,15 @@
342
352
  `check:style`'s rhythm axis sees each of them read a rung, never that they read
343
353
  the same one — the case `--_qm-inset-control` above is cut for. */
344
354
  --_qm-nest: var(--_qm-space-3);
355
+ /* The numerals a list marker is given room for: two digits, so a tenth item's number
356
+ stands inside the leaf as the first's does. In `ch`, so it tracks the face the
357
+ numbers are set in. */
358
+ --_qm-marker-digits: 2ch;
359
+ /* The text column, measured in from a block's own inline inset: a section's vertical
360
+ plus the rung its heading stands off it, so a heading riding that vertical and a
361
+ line beside it start on one edge. A rung for the reason the nesting step is: the
362
+ card and the tip tucked under `main` are two components holding one edge. */
363
+ --_qm-column: calc(var(--_qm-space) + var(--_qm-vertical-width));
345
364
  /* The scroll tail: dead space under the last card, so it can be read at the
346
365
  middle of a pane rather than at its bottom edge. A fraction of the viewport
347
366
  rather than a step on the ramp, because what it answers is how far the eye
@@ -358,6 +377,10 @@
358
377
  rather than through the icon's `size`, so a stylesheet beats the SVG presentation
359
378
  attribute and one rung is the whole of what a control draws. */
360
379
  --_qm-glyph-control: 14px;
380
+ /* The stroke a control glyph draws, in screen length: the frame's 2 units of 24 at
381
+ the control rung. A glyph drawn at another edge reads it under
382
+ `vector-effect: non-scaling-stroke`, so every glyph strokes at one weight. */
383
+ --_qm-glyph-stroke: calc(var(--_qm-glyph-control) / 12);
361
384
  /* The narrowest track a control that fills its width stays usable in: a table's
362
385
  column floors at it, rises no further than its content, and a row of them scrolls
363
386
  sideways rather than crushing a cell. A column drawing a mark takes no floor — a
@@ -374,22 +397,19 @@
374
397
  --_qm-track-prose: calc(var(--_qm-track-min) * 2);
375
398
 
376
399
  /* ── Type ────────────────────────────────────────────────────────────────
377
- `--qm-font-size` anchors the chrome rung; 1.125 is the ratio between adjacent
378
- rungs: a step up (title) and one down (label). The ratio and the
379
- weight are fixed conventions, not dials: no consumer tunes a typographic
380
- ratio, and a dial nobody turns is a name every reader still holds. Three chrome
381
- rungs: a second step below `label` lands near 11px, under a size anything here
382
- wants to be read at, and the supporting text that would take it is what `label`
383
- already is. */
400
+ `--qm-font-size` anchors the chrome rung and `label` stands one 1.125 step under
401
+ it. The ratio and the weight are fixed conventions, not dials: no consumer tunes
402
+ a typographic ratio, and a dial nobody turns is a name every reader still holds.
403
+ Two chrome rungs: nothing the chrome names ranks above the value it names, a card
404
+ title being body at the mid weight, and a second step below `label` lands near
405
+ 11px, under a size anything here wants to be read at. */
384
406
  --_qm-text-body: var(--qm-font-size, 0.875rem);
385
- --_qm-text-title: calc(var(--_qm-text-body) * 1.125);
386
407
  --_qm-text-label: calc(var(--_qm-text-body) / 1.125);
387
408
  /* Paper: the document body answers to the preview's paper where every other
388
409
  surface here answers to the control beside it, so a rung shared with the chrome
389
410
  makes a consumer asking for larger prose take larger inputs with it. 16/15 where
390
411
  the ramp steps at 9/8: small enough to read as a change of register rather than
391
- a rung of hierarchy, and clear of the title rung, which one 9/8 step lands on
392
- exactly. */
412
+ a rung of hierarchy. */
393
413
  --_qm-text-paper: calc(var(--_qm-text-body) * 16 / 15);
394
414
  /* Two weights over the regular, and only one of them is chrome. `mid` names
395
415
  everything the surface names — a card title, a section header, a field
package/dist/index.js CHANGED
@@ -1,9 +1,4 @@
1
- // `@quillmark/svelte` root: the substrate, free of any UI framework.
2
- //
3
- // The headline surfaces mount from their own subpaths (`@quillmark/svelte/preview`,
4
- // `/visual`) so a bundler pulls only what an entry reaches; the root
5
- // re-exports `/core` (the address vocabulary, the error channel, `init`) for a
6
- // consumer that wants one import for the substrate. It carries no framework code.
7
- // The `@quillmark/wasm` API is imported from the peer dependency directly, never
8
- // from here.
1
+ // `@quillmark/svelte` root: the substrate, free of any UI framework. The headline surfaces mount
2
+ // from their own subpaths (`/preview`, `/visual`); the root re-exports `/core` for a consumer that
3
+ // wants one import for the substrate.
9
4
  export * from './core/index.js';
@@ -0,0 +1,71 @@
1
+ /* An app's own chrome: the band that names it, the line that signs it, and the one
2
+ action it exists for. The shell gives a band its row; these give the app's bands their
3
+ look, for a host whose page is one form rather than a workroom. quillkit's app client
4
+ wears them, and an author's `app.css` restyles them (THEMING §"The app chrome").
5
+
6
+ Specificity zero throughout, as the shell's: a rule of the host's own on the same
7
+ element wins at any specificity and in any sheet order. `check:style` holds the rank.
8
+ This file mints nothing. */
9
+
10
+ /* The head: the plate the title stands on, a hairline under it, and the ink everything on
11
+ it takes, so a host recolouring the band recolours its name with it. */
12
+ :where(.qm-app-header) {
13
+ padding: var(--qmh-space-2) var(--qmh-space-4);
14
+ color: var(--qmh-ink);
15
+ background: var(--qmh-surface);
16
+ border-block-end: var(--qmh-border-width) solid var(--qmh-border);
17
+ }
18
+
19
+ /* What the app is called. Display type rather than chrome type: it is the one line on
20
+ the screen a reader reads as a name. */
21
+ :where(.qm-app-title) {
22
+ margin: 0;
23
+ font-size: var(--qmh-text-title);
24
+ font-weight: var(--qmh-weight-strong);
25
+ line-height: var(--qmh-leading-tight);
26
+ color: inherit;
27
+ overflow-wrap: anywhere;
28
+ }
29
+
30
+ /* The foot: a line of meta type on the head's plate under a hairline, its links in the
31
+ line's own ink. The two bands framing the panes stand on one plate. */
32
+ :where(.qm-app-footer) {
33
+ padding: var(--qmh-space-2) var(--qmh-space-4);
34
+ background: var(--qmh-surface);
35
+ border-block-start: var(--qmh-border-width) solid var(--qmh-border);
36
+ font-size: var(--qmh-text-meta);
37
+ line-height: var(--qmh-leading-tight);
38
+ color: var(--qmh-ink-meta);
39
+ }
40
+
41
+ :where(.qm-app-footer a) {
42
+ color: inherit;
43
+ }
44
+
45
+ /* The primary action: the one control on the screen that wears the accent, at the touch
46
+ floor, since it is the press the whole page leads to. */
47
+ :where(.qm-app-primary) {
48
+ font: inherit;
49
+ font-size: var(--qmh-text-label);
50
+ font-weight: var(--qmh-weight-strong);
51
+ line-height: var(--qmh-leading-tight);
52
+ min-height: var(--qmh-tap);
53
+ padding: var(--qmh-space-2) var(--qmh-space-4);
54
+ color: var(--qmh-on-accent);
55
+ background: var(--qmh-accent);
56
+ border: var(--qmh-border-width) solid var(--qmh-accent);
57
+ border-radius: var(--qmh-radius-inner);
58
+ cursor: pointer;
59
+ transition: background-color var(--qmh-duration) var(--qmh-ease-reverse);
60
+ }
61
+
62
+ :where(.qm-app-primary:hover) {
63
+ background: var(--qmh-accent-hover);
64
+ }
65
+
66
+ :where(.qm-app-primary:disabled) {
67
+ color: var(--qmh-ink-meta);
68
+ background: var(--qmh-surface-hover);
69
+ border-color: var(--qmh-border);
70
+ cursor: default;
71
+ }
@@ -1,10 +1,11 @@
1
- /* `@quillmark/svelte/preset`, the package's one CSS entry point: the endorsed look for
2
- the page a surface is mounted on. Opt-in and side-effect free until imported:
3
- the surface barrels pull `core/theme.css` and never this, because this
4
- styles the document and nothing may put that in a consumer's page uninvited.
5
-
6
- Two files because one of them is exempt: `scale.css` is where the literals are
7
- minted, `recipes.css` is held to reading them. */
1
+ /*
2
+ * `@quillmark/svelte/preset`, the package's one CSS entry point: the endorsed look for the page a
3
+ * surface is mounted on. Opt-in: the surface barrels pull `core/theme.css` and never this, because
4
+ * this styles the document. `scale.css` is where the literals are minted; the rest read them.
5
+ * `recipes.css` ranks at ordinary specificity, `shell.css` and `app.css` at zero.
6
+ */
8
7
 
9
8
  @import './scale.css';
10
9
  @import './recipes.css';
10
+ @import './shell.css';
11
+ @import './app.css';
@@ -1,17 +1,15 @@
1
- /* The endorsed look: the rules a host draws around a mounted surface and the shell it
2
- mounts one in, as classes it puts on its own elements. The rungs beside this in
3
- `scale.css` are the invisible half (a scale alone still leaves a consumer writing
4
- every declaration before anything on the page looks like anything), so the recipes
5
- are what the preset is for. This file mints nothing; `check:style` runs its axes here.
6
-
7
- These are host classes. They land on the consumer's DOM, never on a mounted root, so
8
- they freeze nothing about the surfaces' interior: the reason the package withholds
9
- its own internal classes does not reach them.
10
-
11
- Unlayered, deliberately: a layer of the preset's own would put a name in the contract,
12
- and unlayered a consumer's rules win over these by ordinary precedence. Ordinary
13
- specificity too, not the surfaces' `:where()` zero: a consumer takes these classes on
14
- purpose, so a rule of their own beside one is aimed rather than incidental. */
1
+ /*
2
+ * The endorsed look: the rules a host draws around a mounted surface, as classes it puts on its
3
+ * own elements. The shell is `shell.css` and an app's chrome `app.css`; the rungs in `scale.css`
4
+ * are the invisible half. This file mints nothing; `check:style` runs its axes here.
5
+ *
6
+ * These are host classes: they land on the consumer's DOM, never on a mounted root, so they freeze
7
+ * nothing about the surfaces' interior.
8
+ *
9
+ * Unlayered, at ordinary specificity (not the surfaces' `:where()` zero): a consumer takes these
10
+ * classes on purpose, so a rule of their own beside one is aimed rather than incidental, and a
11
+ * layer of the preset's own would put a name in the contract.
12
+ */
15
13
 
16
14
  /* ── The page ──────────────────────────────────────────────────────────────── */
17
15
 
@@ -40,156 +38,6 @@
40
38
  max-width: var(--qmh-measure);
41
39
  }
42
40
 
43
- /* ── The workspace ─────────────────────────────────────────────────────────── */
44
-
45
- /* The rules above dress the page a surface sits on; these four are the shell it is
46
- mounted in, a tool where `.qm-page` is a document: the screen an app that mounts two
47
- surfaces over one session is, drawn once (ARCHITECTURE §Styling).
48
-
49
- Pinned to the viewport rather than sized to it: a page-height box still leaves the
50
- root scroller counting the overflow its panes clip, and a wheel over the chrome drags
51
- the whole app off the top. Three bands (a head, the body, an optional foot), and the
52
- body takes what is left and may shrink below its content, so the panes in it own
53
- their overflow and hold still while their contents move.
54
-
55
- The one column is stated for the same reason the rows are: an implicit track is `auto`,
56
- which is sized by what a band holds, and a `.qm-split` under one contributes its items'
57
- max-content through its `fr` tracks. The shell would take the width of the widest thing
58
- mounted in it instead of the viewport's, and every band would be that wide with the
59
- second pane past the edge. A host holding its content to a maximum never sees it; one
60
- spending the whole screen sees it on the first surface it mounts. */
61
- .qm-workspace {
62
- position: fixed;
63
- inset: 0;
64
- display: grid;
65
- grid-template-columns: minmax(0, 1fr);
66
- grid-template-rows: auto minmax(0, 1fr) auto;
67
- overflow: hidden;
68
- }
69
-
70
- /* A gesture past the end of a pane chains out to the viewport and bounces the empty
71
- document, which is the last way a pinned shell moves. Conditioned on the workspace, so
72
- a host's other routes keep a page's own feel. */
73
- html:has(.qm-workspace) {
74
- overscroll-behavior: none;
75
- }
76
-
77
- /* A band's line: the row a head or a foot puts its parts on, wrapping, centred. That is
78
- the whole of it. What a band is made of is the host's: the rule under it, the gutter
79
- beside it, and the depth of it, because where a band ends and how tall it stands are
80
- page-shell decisions: a site holding its content to a maximum runs the rule past it at
81
- its own rhythm, and a one-screen tool has no maximum to hold to. */
82
- .qm-bar {
83
- display: flex;
84
- flex-wrap: wrap;
85
- align-items: center;
86
- gap: var(--qmh-space-2) var(--qmh-space-4);
87
- }
88
-
89
- /* Two even tracks for two surfaces, and no resizer, since a divider that can be dragged
90
- is a control competing with what it divides. Each track may shrink below its content,
91
- which is what lets the frame in it clip rather than grow.
92
-
93
- The gap is the endorsed default and not a fact about splitting: two surfaces standing
94
- apart on a page read as two things, which is what a host showing its instruments wants.
95
- A host that spends the whole screen on the mounts closes it to nothing and carries a
96
- hairline on one pane instead: one property, and the tracks are untouched. */
97
- .qm-split {
98
- display: grid;
99
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
100
- grid-template-rows: minmax(0, 1fr);
101
- gap: var(--qmh-space-4);
102
- min-height: 0;
103
- }
104
-
105
- /* The band that switches them, and nothing at the widths where both tracks are showing
106
- and there is nothing to switch. Where it stands is the host's, like any other band; what
107
- goes in it is a pair of `.qm-control`s, which already carry the on-state a pressed
108
- control takes. Its visibility is the preset's because the threshold is: a host hiding
109
- its own tab row writes this file's number a second time, which is the one thing the
110
- single source is for. */
111
- .qm-switch {
112
- display: none;
113
- }
114
-
115
- /* One mount at a time under the threshold, rather than two of them abreast. Half a narrow
116
- viewport is under the width either surface reads at, so the split stops being one and
117
- the tracks become views of the same room: the host says which with `data-qm-show`, and
118
- the attribute names the track rather than what is in it, so nothing here knows an editor
119
- from a preview. No attribute is the first track.
120
-
121
- The other track is `display: none` and not a clip, which is the whole of the hiding: it
122
- leaves the tab order, hit-testing and the accessibility tree in one property, so the
123
- pane needs no `inert` and the host needs no breakpoint in JS. Both stay mounted (a
124
- preview binds its handle once, an editor holds a caret, a history and a scroll), so
125
- the switch is presentational, never conditional. A pane hidden this way keeps the
126
- pixels it had: the paint loop's band sweep no-ops on an empty visible set, and its
127
- resize observer holds the last real width rather than rastering at the 0×0 box a hidden
128
- pane reports, so what comes back at the width it left needs no paint at all. The
129
- preview's caret follow spans the same window — a trip asked for against a pane with no
130
- box is held and run when it has one (`preview/bridge.ts`), the pane opening at the
131
- caret rather than several edits behind it.
132
-
133
- Height is the second clause because a landscape phone passes the first one: it clears
134
- the width with room for two tracks and has no body left to put them in. A desktop window
135
- that short wants one mount for the same reason. */
136
- @media (width < 50rem), (height < 30rem) {
137
- .qm-switch {
138
- display: flex;
139
- gap: var(--qmh-space);
140
- }
141
-
142
- /* Even halves of the band, at the height a thumb needs. The floor is the recipe's and
143
- not the host's, because the band exists only at the sizes where the pointer is a
144
- finger: a host is free to stand the band deeper, and has nothing to gain from
145
- standing it shallower than the thing it is tapping. */
146
- .qm-switch > * {
147
- flex: 1 1 0;
148
- min-height: var(--qmh-tap);
149
- }
150
-
151
- /* Centred, which is what a control filling half a band reads as; `.qm-control` itself
152
- reads left, for the menus it also dresses. */
153
- .qm-switch > .qm-control {
154
- text-align: center;
155
- }
156
-
157
- .qm-split {
158
- grid-template-columns: minmax(0, 1fr);
159
- }
160
-
161
- .qm-split:not([data-qm-show='2']) > :nth-child(2),
162
- .qm-split[data-qm-show='2'] > :nth-child(1) {
163
- display: none;
164
- }
165
- }
166
-
167
- /* The mounting site: an edge, a corner, and the clip that holds a surface to them, which
168
- is all a surface painting its own column needs. No tone and no gutter: those are the
169
- surface's own (THEMING §"Drop it in"). `border-box`, so a stated height is the height
170
- it draws.
171
-
172
- Positioned, because a clip reaches only what it is the containing block for: a
173
- surface's out-of-flow parts (a parked description, an overlay) resolve against the
174
- nearest positioned ancestor, and with none between them and the document they land at
175
- their static position unclipped, leaving the page to scroll past its own foot by
176
- however far the frame's contents run over.
177
-
178
- The two minimums are what let a frame in a track clip rather than push: a grid or flex
179
- item floors at its content, so a mount that paints wider than its column widens the
180
- column instead. They ride the frame rather than the split's children, which keeps every
181
- rule in this file landing where the host put a class. Outside a track they are the
182
- initial value and do nothing. */
183
- .qm-frame {
184
- box-sizing: border-box;
185
- position: relative;
186
- min-width: 0;
187
- min-height: 0;
188
- border: var(--qmh-border-width) solid var(--qmh-border);
189
- border-radius: var(--qmh-radius);
190
- overflow: hidden;
191
- }
192
-
193
41
  /* ── Type ──────────────────────────────────────────────────────────────────── */
194
42
 
195
43
  /* A run of chrome type: a section's label, a panel's caption, the name over a readout.
@@ -306,6 +154,13 @@ pre.qm-readout {
306
154
  background: var(--qmh-surface-hover);
307
155
  }
308
156
 
157
+ /* Centred in the shell's switch band, which is what a control filling half a band reads
158
+ as; `.qm-control` itself reads left, for the menus it also dresses. Here rather than
159
+ beside the band in `shell.css`, which ranks at zero and so could not outrank this. */
160
+ .qm-switch > .qm-control {
161
+ text-align: center;
162
+ }
163
+
309
164
  /* One choice means nothing to pick: the control stays, because what it reads is a fact
310
165
  worth having, and it recedes to say it is not a choice. */
311
166
  .qm-control:disabled {
@@ -1,13 +1,13 @@
1
1
  /* The host's derivation: the same public dials in, a closed `--qmh-*` scale for the
2
2
  page around the surfaces out. `core/theme.css` is the same shape one level down
3
3
  (dials in, `--_qm-*` out), and the two are calibrated against each other: the
4
- figures here are the figures there (the ground step's ramp, 8% hover, the
5
- ring's width, the status hue at both poles) and the
4
+ figures here are the figures there (the poles, the ground step's ramp, 8% hover, the
5
+ label ink's 35%, the ring's width, the status hue at both poles) and the
6
6
  dial means the same plane in both, so a host plate and a card on it land on the same
7
7
  tone at both poles and a host page meets an editor's own column exactly. The border
8
- and ink rungs are each calibrated for their own plane rather than shared: a page's
9
- chrome is read at one size against one ground, a card's interior at several against
10
- the rungs under it, so the mixes are not the same figure. The well
8
+ is calibrated for its own plane rather than shared: a page's controls are boxed and
9
+ read against one ground, a card's strokes at several weights against the rungs
10
+ under it, so the mixes are not the same figure. The well
11
11
  under the ground has no counterpart here: a host's own controls are boxed and carry a
12
12
  hairline, so nothing on a page needs the step an edgeless field does.
13
13
 
@@ -18,11 +18,10 @@
18
18
  could not be both.
19
19
 
20
20
  Literals live here and nowhere else in the preset: `check:style` runs its axes over
21
- `recipes.css` beside it, with this file exempt as the place the defaults are minted.
21
+ the sheets beside it, with this file exempt as the place the defaults are minted.
22
22
 
23
- The poles fall back to the system colours, so a host that turns no dial still lands
24
- where its own unstyled text does — `Canvas` being the plane the UA would have
25
- painted a page with, which is what a plate should be. The dials sit in front, so
23
+ The poles fall back to the surfaces' own pair, so a host that turns no dial lands on
24
+ the plane an editor mounted in it paints at both poles. The dials sit in front, so
26
25
  `--qm-bg`/`--qm-fg` on an ancestor retunes page and surface together. */
27
26
 
28
27
  :root {
@@ -32,21 +31,28 @@
32
31
  the page sinks from it by the same ramp. That is what puts a host page and
33
32
  an editor's own column on one tone rather than a plane apart. Planes step by
34
33
  light and everything else by ink, which is the package's own split
35
- (ARCHITECTURE §Styling); the figures are its figures. `Canvas` flips with the
36
- declared scheme on its own, so the ramp reads whichever plane it resolved to
37
- rather than trusting an arm to match. */
38
- --qmh-surface: var(--qm-bg, Canvas);
39
- --qmh-page: oklab(from var(--qmh-surface) calc(l - (0.0214 + 0.0467 * (1 - l))) a b);
40
- --qmh-ink: var(--qm-fg, CanvasText);
34
+ (ARCHITECTURE §Styling); the figures are its figures. */
35
+ --qmh-surface: var(--qm-bg, light-dark(#fff, #272729));
36
+ --qmh-page: oklab(from var(--qmh-surface) calc(l - (0.04 + 0.0202 * (1 - l))) a b);
37
+ --qmh-ink: var(--qm-fg, light-dark(#1c1c1c, #d7dadc));
41
38
  /* One step off the ink and no second: a page's chrome type is small, tracked and
42
39
  uppercase, so a rung below this reads under 4.5:1 against the plate and the
43
40
  label and status lines that would take it are text (WCAG 1.4.3). */
44
- --qmh-ink-meta: color-mix(in oklab, var(--qmh-ink), var(--qmh-surface) 45%);
41
+ --qmh-ink-meta: color-mix(in oklab, var(--qmh-ink), var(--qmh-surface) 35%);
45
42
  --qmh-surface-hover: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 8%);
46
43
  --qmh-border: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 17%);
47
44
  /* The edge that says "engaged" where the hairline says "edge": a pressed switch,
48
- the underline under a link. */
49
- --qmh-border-strong: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 40%);
45
+ the underline under a link. Half way to the ink, which clears WCAG 1.4.11's 3:1
46
+ against the page and the plate at both poles. */
47
+ --qmh-border-strong: color-mix(in oklab, var(--qmh-surface), var(--qmh-ink) 50%);
48
+
49
+ /* The one hue a host's page spends on purpose: the primary action wears it. The
50
+ figures are the surfaces' own default for the same dial, so a page that turns no
51
+ dial and a card's focus ring agree; the ink on it is the plate's, the pole the
52
+ accent's default contrasts against in both schemes. */
53
+ --qmh-accent: var(--qm-accent, light-dark(#71717a, #a1a1aa));
54
+ --qmh-on-accent: var(--qmh-surface);
55
+ --qmh-accent-hover: color-mix(in oklab, var(--qmh-accent), var(--qmh-ink) 15%);
50
56
 
51
57
  /* The hue a host spends on a failure. The figures are the surfaces' own, so a
52
58
  page's status line and a card's diagnostic carry one colour meaning error.
@@ -54,6 +60,9 @@
54
60
  on a dark page. The dial sits in front of the pair, as every pole's does, so
55
61
  a host that turns one owns both schemes. */
56
62
  --qmh-alert: var(--qm-danger, light-dark(#9e1518, #f9786c));
63
+ /* What a modal's backdrop lays over the page: the ground held translucent, so the
64
+ page recedes under the dialog without a second tone to read. */
65
+ --qmh-scrim: color-mix(in srgb, var(--qmh-page) 80%, transparent);
57
66
 
58
67
  /* ── Rhythm ──────────────────────────────────────────────────────────────── */
59
68
  /* One base and its multiples, the same four steps the surfaces derive. A host
@@ -92,6 +101,7 @@
92
101
  surfaces' ramp, which is chrome inside a card, and a page's body text is a
93
102
  different job at a different size. A host that wants them locked sets both. */
94
103
  --qmh-text: 1rem;
104
+ --qmh-text-title: 1.125rem;
95
105
  --qmh-text-label: 0.8125rem;
96
106
  --qmh-text-meta: 0.6875rem;
97
107
  --qmh-weight-strong: 600;