@quillmark/svelte 0.1.1 → 0.3.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 (124) hide show
  1. package/README.md +16 -12
  2. package/THEMING.md +48 -25
  3. package/dist/core/address.d.ts +42 -7
  4. package/dist/core/address.js +49 -21
  5. package/dist/core/bloom.d.ts +5 -5
  6. package/dist/core/bloom.js +8 -8
  7. package/dist/core/codec/anchor.d.ts +1 -1
  8. package/dist/core/codec/anchor.js +1 -1
  9. package/dist/core/codec/atoms.d.ts +10 -0
  10. package/dist/core/codec/atoms.js +78 -0
  11. package/dist/core/codec/clipboard.d.ts +59 -0
  12. package/dist/core/codec/clipboard.js +202 -0
  13. package/dist/core/codec/code.d.ts +1 -1
  14. package/dist/core/codec/code.js +4 -4
  15. package/dist/core/codec/decode.d.ts +1 -1
  16. package/dist/core/codec/decode.js +8 -8
  17. package/dist/core/codec/encode.d.ts +4 -4
  18. package/dist/core/codec/encode.js +14 -11
  19. package/dist/core/codec/field.d.ts +22 -27
  20. package/dist/core/codec/field.js +33 -32
  21. package/dist/core/codec/index.js +5 -8
  22. package/dist/core/codec/inputrules.js +56 -18
  23. package/dist/core/codec/islands.d.ts +11 -7
  24. package/dist/core/codec/islands.js +16 -10
  25. package/dist/core/codec/keymap.d.ts +5 -6
  26. package/dist/core/codec/keymap.js +8 -7
  27. package/dist/core/codec/lists.d.ts +3 -3
  28. package/dist/core/codec/lists.js +32 -13
  29. package/dist/core/codec/marks.d.ts +1 -1
  30. package/dist/core/codec/marks.js +1 -1
  31. package/dist/core/codec/positions.js +1 -1
  32. package/dist/core/codec/prose.css +266 -228
  33. package/dist/core/codec/schema.js +4 -4
  34. package/dist/core/codec/slash.d.ts +39 -41
  35. package/dist/core/codec/slash.js +0 -0
  36. package/dist/core/codec/table-view.d.ts +12 -16
  37. package/dist/core/codec/table-view.js +488 -292
  38. package/dist/core/codec/table.d.ts +42 -15
  39. package/dist/core/codec/table.js +73 -27
  40. package/dist/core/errors.d.ts +17 -7
  41. package/dist/core/index.d.ts +1 -1
  42. package/dist/core/index.js +6 -5
  43. package/dist/core/lifecycle.d.ts +21 -5
  44. package/dist/core/lifecycle.js +34 -8
  45. package/dist/core/rebind.svelte.d.ts +18 -4
  46. package/dist/core/rebind.svelte.js +30 -12
  47. package/dist/core/teardown.d.ts +1 -1
  48. package/dist/core/teardown.js +1 -1
  49. package/dist/core/theme.css +173 -58
  50. package/dist/preset/index.css +1 -1
  51. package/dist/preset/recipes.css +79 -30
  52. package/dist/preset/scale.css +40 -28
  53. package/dist/preview/Preview.svelte +22 -19
  54. package/dist/preview/Preview.svelte.d.ts +8 -8
  55. package/dist/preview/bridge.d.ts +6 -4
  56. package/dist/preview/bridge.js +19 -9
  57. package/dist/preview/controller.d.ts +13 -6
  58. package/dist/preview/controller.js +8 -5
  59. package/dist/preview/geometry.d.ts +17 -2
  60. package/dist/preview/geometry.js +21 -1
  61. package/dist/preview/index.d.ts +1 -0
  62. package/dist/preview/index.js +9 -2
  63. package/dist/preview/overlay.d.ts +2 -1
  64. package/dist/preview/overlay.js +26 -11
  65. package/dist/preview/paint.js +19 -17
  66. package/dist/visual/ArrayField.svelte +103 -83
  67. package/dist/visual/ArrayField.svelte.d.ts +5 -4
  68. package/dist/visual/BooleanField.svelte +8 -6
  69. package/dist/visual/BooleanField.svelte.d.ts +2 -2
  70. package/dist/visual/Card.svelte +161 -333
  71. package/dist/visual/Card.svelte.d.ts +5 -4
  72. package/dist/visual/CardControls.svelte +1 -1
  73. package/dist/visual/DateField.svelte +19 -19
  74. package/dist/visual/DateField.svelte.d.ts +3 -3
  75. package/dist/visual/DiagnosticList.svelte +2 -2
  76. package/dist/visual/EnumField.svelte +48 -28
  77. package/dist/visual/EnumField.svelte.d.ts +5 -4
  78. package/dist/visual/Field.svelte +207 -132
  79. package/dist/visual/Field.svelte.d.ts +11 -7
  80. package/dist/visual/FieldHint.svelte +79 -48
  81. package/dist/visual/FieldHint.svelte.d.ts +1 -1
  82. package/dist/visual/FieldLabel.svelte +8 -8
  83. package/dist/visual/FormatPopover.svelte +82 -35
  84. package/dist/visual/NumberField.svelte +5 -5
  85. package/dist/visual/NumberField.svelte.d.ts +1 -1
  86. package/dist/visual/ObjectField.svelte +28 -9
  87. package/dist/visual/ObjectField.svelte.d.ts +4 -2
  88. package/dist/visual/ProseArrayElement.svelte +12 -19
  89. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -2
  90. package/dist/visual/ProseField.svelte +62 -85
  91. package/dist/visual/ProseField.svelte.d.ts +14 -17
  92. package/dist/visual/SlashMenu.svelte +42 -16
  93. package/dist/visual/TextField.svelte +5 -5
  94. package/dist/visual/TextField.svelte.d.ts +1 -1
  95. package/dist/visual/TipsCard.svelte +43 -28
  96. package/dist/visual/VisualEditor.svelte +18 -19
  97. package/dist/visual/VisualEditor.svelte.d.ts +2 -3
  98. package/dist/visual/VisualEditorInner.svelte +251 -184
  99. package/dist/visual/VisualEditorInner.svelte.d.ts +2 -3
  100. package/dist/visual/controls.css +85 -38
  101. package/dist/visual/diagnostics.d.ts +13 -21
  102. package/dist/visual/diagnostics.js +7 -19
  103. package/dist/visual/domid.js +3 -3
  104. package/dist/visual/ext.d.ts +1 -1
  105. package/dist/visual/ext.js +1 -1
  106. package/dist/visual/icons/nodes.d.ts +3 -0
  107. package/dist/visual/icons/nodes.js +2 -1
  108. package/dist/visual/index.d.ts +1 -0
  109. package/dist/visual/index.js +10 -3
  110. package/dist/visual/leaves.d.ts +47 -0
  111. package/dist/visual/leaves.js +31 -0
  112. package/dist/visual/links.d.ts +24 -0
  113. package/dist/visual/links.js +67 -0
  114. package/dist/visual/props.d.ts +33 -12
  115. package/dist/visual/signals.d.ts +6 -6
  116. package/dist/visual/strings.d.ts +14 -19
  117. package/dist/visual/strings.js +5 -7
  118. package/dist/visual/structure.d.ts +24 -17
  119. package/dist/visual/structure.js +8 -8
  120. package/dist/visual/synced.svelte.d.ts +1 -1
  121. package/dist/visual/synced.svelte.js +4 -4
  122. package/dist/visual/tips.d.ts +4 -2
  123. package/dist/visual/tips.js +7 -4
  124. package/package.json +2 -2
@@ -1,10 +1,10 @@
1
1
  /* The one derivation: public dials in, a closed private scale out (THEMING.md).
2
2
  Everything below reads a rung rather than minting a value, which is this file's
3
3
  whole reason for existing and what `check:style` holds everywhere else. Colour,
4
- geometry, type and motion are all derived here, so each is minted in ONE place
4
+ geometry, type and motion are all derived here, so each is minted in one place
5
5
  instead of once per detached root.
6
6
 
7
- TRANSPORT: a stylesheet, side-effect imported by `core/index.ts` and applied to
7
+ Transport: a stylesheet, side-effect imported by `core/index.ts` and applied to
8
8
  every element marked `data-qm-root`. The package imports it itself, so there is
9
9
  no `import './theme.css'` for a consumer to forget. A stylesheet rather than an
10
10
  inline `style` attribute is what buys the three things an attribute cannot
@@ -19,21 +19,21 @@
19
19
  chrome that still reads as intact. `check:bundle` holds both ends, the claim and
20
20
  the built bytes.
21
21
 
22
- Every value stays a `var()` reference, so the dials resolve against the CASCADE
22
+ Every value stays a `var()` reference, so the dials resolve against the cascade
23
23
  at each root: a consumer's `[data-theme="dark"]` ancestor rule flows in
24
24
  unchanged, the light/dark default follows the `color-scheme` the host declares
25
25
  with no JS and no media query, and `color-mix` runs at paint time. The rungs are
26
- declared ON the root, so they are reachable only by a rule targeting it:
26
+ declared on the root, so they are reachable only by a rule targeting it:
27
27
  THEMING.md §"What is deliberately not public" states the terms.
28
28
 
29
- LAYERS: `qm` is the top-level layer name, and it is contract — a consumer
29
+ Layers: `qm` is the top-level layer name, and it is contract — a consumer
30
30
  pins the order against it with `@layer qm, app;` in their own CSS, which wins
31
31
  in both bundler orders (THEMING §"Your CSS beats ours"). Unlayered consumer
32
32
  CSS beats every layer here for free.
33
33
 
34
- `@property` registers the three ENTRY rungs, the ones reading a dial, and
34
+ `@property` registers the three entry rungs, the ones reading a dial, and
35
35
  nothing below them. An `initial-value` must be computationally independent,
36
- which `rem` is not, so the DIALS cannot be registered: their defaults are
36
+ which `rem` is not, so the dials cannot be registered: their defaults are
37
37
  `rem`. A rung can, because registration reads the initial value and never the
38
38
  declared one, so a `px` initial sits under a `calc()` chain over a `rem` dial
39
39
  without contradiction. What it buys is the poison case: `--qm-space: 4` is a
@@ -69,19 +69,23 @@
69
69
  @layer qm.scale {
70
70
  :where([data-qm-root]) {
71
71
  /* ── Poles ───────────────────────────────────────────────────────────────
72
- Each dial's default, stated ONCE. Every rung below reads a pole, never a
72
+ Each dial's default, stated once. Every rung below reads a pole, never a
73
73
  dial, so both schemes cost two declarations rather than fifteen; and the
74
- dial sits in FRONT of the default, so a consumer's ancestor value wins in
74
+ dial sits in front of the default, so a consumer's ancestor value wins in
75
75
  either scheme, which it would not if a scheme rule set `--qm-bg` instead.
76
76
 
77
- `light-dark()` keys the default off the `color-scheme` this root INHERITS,
77
+ `light-dark()` keys the default off the `color-scheme` this root inherits,
78
78
  which is the host's: an app that declares `dark` gets a dark surface
79
79
  whatever the OS says, and one that declares nothing gets light, matching
80
80
  its own unstyled text. So the package owns no mode logic at all: light or
81
81
  dark is the mounting site's, like every other value here (THEMING.md
82
- §"The surface follows your colour scheme"). */
83
- --_qm-bg: var(--qm-bg, light-dark(#fff, #14171c));
84
- --_qm-fg: var(--qm-fg, light-dark(#1a1a1a, #e8eaed));
82
+ §"The surface follows your colour scheme").
83
+
84
+ `--qm-bg` is the card, not the column behind it: the plane content is
85
+ written on, which is also the preview's paper. Everything else is a step
86
+ off it, and the column is a step down. */
87
+ --_qm-bg: var(--qm-bg, light-dark(#fff, #272729));
88
+ --_qm-fg: var(--qm-fg, light-dark(#1c1c1c, #d7dadc));
85
89
  --_qm-accent: var(--qm-accent, #2563eb);
86
90
  --_qm-danger: var(--qm-danger, #c5221f);
87
91
  --_qm-warning: var(--qm-warning, #b25000);
@@ -89,33 +93,103 @@
89
93
  --_qm-font-mono: var(--qm-font-mono, ui-monospace, monospace);
90
94
 
91
95
  /* ── Colour ──────────────────────────────────────────────────────────────
92
- Surfaces step bg → fg, ink steps fg → bg, mixed in oklab; sRGB muddies
93
- the mid-tones and the label/meta/border rungs are exactly mid-tone. So
94
- inverting the poles inverts the scale. The percentages are calibrated to
95
- the light-mode literals they stand in for (17% ≈ #d4d4d4, 35% ≈ #555).
96
+ Ink steps fg → bg, the border and hover rungs step bg → fg, all mixed in
97
+ oklab; sRGB muddies the mid-tones and the label/meta/border rungs are
98
+ exactly mid-tone. So inverting the poles inverts that half of the scale.
99
+ The percentages are calibrated to the light-mode literals they stand in
100
+ for (17% ≈ #d4d4d4, 35% ≈ #555).
101
+
102
+ Planes are the half that does not read the ink pole, because what makes a
103
+ card read as a card is that it is the brighter plane, in both schemes
104
+ alike (ARCHITECTURE §Styling). A plane stepped toward the ink is right in
105
+ dark and inverted in light, where it darkens the card below the page it
106
+ floats on. So `sunken` and `well` step toward shadow and `raised` toward
107
+ light, and the base plane — a card, the preview's paper — is the pole
108
+ itself.
109
+
110
+ Two planes sink. `sunken` is the ground — the column behind the cards, the
111
+ desk behind the sheets — and lifting the plane above it takes very little
112
+ at the light pole. `well` is the hole cut into a card: a field, a table's
113
+ header band, the one native select, none of which draw an edge, so the
114
+ step is the whole of what says a value is typed there. The order at the
115
+ light pole is card, ground, well; at the dark pole the two sink to within
116
+ a channel of each other, which is where a card with little light left to
117
+ give up leaves them.
118
+
119
+ Each takes two arms, because one figure cannot serve both poles: a dark
120
+ surface has to spend more of what little it has before the card above it
121
+ reads as lifted. Pinning a dark palette under a light `color-scheme` takes
122
+ the light arm with it
123
+ (THEMING §"The surface follows your colour scheme"), which is a step you
124
+ can barely see, so a host that pins declares the scheme with it.
96
125
 
97
- The three surface rungs sit EVENLY 4pp apart, which is what keeps them three
98
- distinguishable steps: uneven spacing spends one rung to buy another:
99
- widening `raised` alone would leave `hover` 1pp above it. `raised` steps
100
- toward the INK pole rather than away from it, so a card is darker than the
101
- page in light and lighter in dark. */
126
+ The ground steps by subtraction where every other rung mixes: a mix toward
127
+ black is a ratio, so it scales what it takes and pulls the result toward
128
+ neutral, and a ground is a fixed distance that keeps the pole's hue. Its
129
+ arms are 0.0214 of oklab L in light and 0.0553 in dark, with a/b terms
130
+ carrying a cool cast a mix toward black would strip, so the default lands
131
+ on `#f7f8f9` and `#1a1a1b` exactly and a turned `--qm-bg` tints the plane
132
+ under it.
133
+
134
+ `raised` is a no-op at the light pole, where the card is already white
135
+ and there is nowhere further to go, so a floating surface separates on
136
+ its hairline in light and on the step in dark. */
102
137
  --_qm-surface: var(--_qm-bg);
103
- --_qm-surface-raised: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 4%);
138
+ --_qm-surface-sunken: light-dark(
139
+ oklab(from var(--_qm-bg) calc(l - 0.0214) calc(a - 0.00065) calc(b - 0.00158)),
140
+ oklab(from var(--_qm-bg) calc(l - 0.0553) calc(a - 0.00048) calc(b + 0.00165))
141
+ );
142
+ --_qm-surface-well: light-dark(
143
+ color-mix(in oklab, var(--_qm-bg), #000 4%),
144
+ color-mix(in oklab, var(--_qm-bg), #000 20%)
145
+ );
146
+ --_qm-surface-raised: color-mix(in oklab, var(--_qm-bg), #fff 8%);
104
147
  --_qm-surface-hover: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 8%);
105
148
  --_qm-border: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 17%);
106
- /* A rule that separates without structuring: the lines BETWEEN a table's cells,
149
+ /* A rule that separates without structuring: the lines between a table's cells,
107
150
  where the frame around them is the structure. Half the border's mix rather
108
- than a fourth surface rung, because it is a stroke and reads against the
151
+ than a surface rung of its own, because it is a stroke and reads against the
109
152
  surface behind it, not a fill stacked on one. */
110
153
  --_qm-border-faint: color-mix(in oklab, var(--_qm-bg), var(--_qm-fg) 9%);
154
+ /* A fourth step between `label` and `ghost` is a number with no fourth kind of
155
+ text under it. */
111
156
  --_qm-ink: var(--_qm-fg);
112
157
  --_qm-ink-label: color-mix(in oklab, var(--_qm-fg), var(--_qm-bg) 35%);
113
- --_qm-ink-meta: color-mix(in oklab, var(--_qm-fg), var(--_qm-bg) 45%);
114
158
  --_qm-ink-ghost: color-mix(in oklab, var(--_qm-fg), var(--_qm-bg) 60%);
115
- /* The correlation wash (`core/bloom.ts`). Its alpha is a PEAK, not a tint: the
159
+ /* The correlation wash (`core/bloom.ts`). Its alpha is a peak, not a tint: the
116
160
  wash animates from and back to zero, never held, so it reads at pane scale
117
161
  over the preview's paper while the paper rests clean. */
118
162
  --_qm-accent-wash: color-mix(in oklab, var(--_qm-accent) 18%, transparent);
163
+ /* The accent held: what a selected line of a table is tinted with, and what its
164
+ grip is filled with, so the two ends of one gesture are one colour. A second
165
+ name rather than the wash reused, because the two answer different questions
166
+ and the numbers follow: a peak passes over paper and can be strong, where a
167
+ tint is held under body text and cannot. Translucent, so it
168
+ stacks over whichever surface rung the cell already draws instead of replacing
169
+ it. */
170
+ --_qm-accent-tint: color-mix(in oklab, var(--_qm-accent) 12%, transparent);
171
+ /* The danger hue held: what a destructive control fills with where the family's
172
+ neutral hover rung would read as one more button. Mixed at the accent tint's
173
+ strength, so a held tint is a held tint whichever hue it carries. */
174
+ --_qm-danger-tint: color-mix(in oklab, var(--_qm-danger) 12%, transparent);
175
+ /* The label ink held: what a trigger fills with whose own ink never steps, so
176
+ the fill arriving under it is that tone at a strength a surface reads
177
+ through. */
178
+ --_qm-add-fill: color-mix(in oklab, var(--_qm-ink-label) 15%, transparent);
179
+
180
+ /* The tips card'S hue: the surface's one exit from the greyscale that is not
181
+ a status, and what the amber and the typewriter face say together is
182
+ draft (VISUAL_EDITOR §"Card operations"). A literal rather than a dial
183
+ because there is nothing left to turn: the three rungs under it mix over
184
+ the surface and the ink a consumer already tuned, so the card follows a
185
+ brand untold. The mixes are faint on purpose: a warmth, not a badge. */
186
+ --_qm-tips: #e88c0a;
187
+ --_qm-tips-surface: color-mix(in oklab, var(--_qm-surface), var(--_qm-tips) 4%);
188
+ --_qm-tips-ink: color-mix(in oklab, var(--_qm-ink), var(--_qm-tips) 24%);
189
+ /* The chip behind a token in a tip. The card's own face is already
190
+ monospace, so a `code` run needs a fill to read as literal text rather
191
+ than as the sentence around it. */
192
+ --_qm-tips-chip: color-mix(in oklab, var(--_qm-tips) 12%, transparent);
119
193
 
120
194
  /* ── Ratios and effects ──────────────────────────────────────────────────
121
195
  No palette fixes these, so they are their own rungs. There is no shadow
@@ -135,13 +209,18 @@
135
209
  --_qm-opacity-muted: 0.5;
136
210
 
137
211
  /* ── Focus ───────────────────────────────────────────────────────────────
138
- These fix the ring's VALUE, so no control mints it. Which properties draw a
212
+ These fix the ring's value, so no control mints it. Which properties draw a
139
213
  ring, and on what state, is the other half: a rule, `.qm-focus-ring` in
140
214
  `visual/controls.css`, since a rung each control reassembles is a scale
141
- only up to the point where it is assembled. ONE width: focus is the only
215
+ only up to the point where it is assembled. One width: focus is the only
142
216
  state that draws a ring anywhere, so there is no held-back tier under it. */
143
217
  --_qm-ring-width: 2px;
144
- --_qm-ring-focus: var(--_qm-ring-width) solid var(--_qm-accent);
218
+ /* Every ring the surface draws, whatever the state under it: focus and selection
219
+ alike, which is why the name carries neither. Assembled here because the parts
220
+ spelled out at a use site pass every axis — each reads a rung — while drawing a
221
+ ring nothing else agrees with. The width stays separately reachable: a drop
222
+ indicator is the ring's thickness without being a ring. */
223
+ --_qm-ring: var(--_qm-ring-width) solid var(--_qm-accent);
145
224
  --_qm-ring-offset: 1px;
146
225
 
147
226
  /* ── Motion ──────────────────────────────────────────────────────────────
@@ -154,15 +233,18 @@
154
233
  animates over WAAPI and reads it from here. */
155
234
  --_qm-bloom-dwell: 1100ms;
156
235
 
157
- /* One curve per role, the role being what the trip's two endpoints ARE.
158
- `reverse` is the only one that has to be symmetric: it is the only one an
159
- interruption turns around. */
236
+ /* One curve, symmetric because an interruption turns these trips around: a hover
237
+ leaves before it arrives, a card collapses mid-expand. Every trip at `fast` or
238
+ `slow` takes it; a one-way shape is legible only over a longer one. */
160
239
  --_qm-ease-reverse: cubic-bezier(0.4, 0, 0.6, 1);
161
- --_qm-ease-arrive: cubic-bezier(0, 0, 0.2, 1);
162
- --_qm-ease-depart: cubic-bezier(0.4, 0, 1, 1);
240
+ /* A trip rather than a rung, duration and curve composed the way `--_qm-ring`
241
+ composes its three: neither half is reachable alone. Weighted to its end, so an
242
+ arrival settles into its rest instead of stopping at it, and long enough that a
243
+ control resting at nothing reads as arriving rather than as switching. */
244
+ --_qm-reveal: 500ms cubic-bezier(0.4, 0, 0.2, 1);
163
245
 
164
246
  /* ── Geometry ──────────────────────────────────────────────────────────*/
165
- /* ONE stroke width for every edge the chrome draws: a card's, a control's,
247
+ /* One stroke width for every edge the chrome draws: a card's, a control's,
166
248
  and the metadata bracket's three rules alike. A threshold rather than a
167
249
  rhythm choice, like the tap floor: a hairline is a hairline at any
168
250
  `--qm-space`, so it does not derive from a dial. It exists as a rung because
@@ -171,6 +253,9 @@
171
253
  against a token. */
172
254
  --_qm-border-width: 1px;
173
255
  --_qm-radius: var(--qm-radius, 8px);
256
+ /* The interior corner, and half is the relationship rather than the number: a
257
+ corner inside a corner reads as nested only while it is visibly tighter, and
258
+ halving holds that at every setting of the dial, zero included. */
174
259
  --_qm-radius-inner: calc(var(--_qm-radius) / 2);
175
260
  /* A fully-rounded end cap: a shape tier, not a step on the radius ramp, so
176
261
  it does not derive from the dial: a pill stays a pill at any radius. */
@@ -180,8 +265,15 @@
180
265
  --_qm-space-2: calc(var(--_qm-space) * 2);
181
266
  --_qm-space-3: calc(var(--_qm-space) * 3);
182
267
  --_qm-space-4: calc(var(--_qm-space) * 4);
183
- /* The scroll TAIL: dead space under the last card, so it can be read at the
184
- middle of a pane rather than at its bottom edge. A fraction of the VIEWPORT
268
+ /* A rung because the two axes are two decisions and the ladder alone cannot hold
269
+ them together: `check:style`'s rhythm axis sees each box read a rung, never that
270
+ they read the same one, which is the drift `visual/controls.css` already names
271
+ for the type half. Asymmetric because the vertical is what leading leaves once
272
+ it has taken the line box, and the horizontal is how far in the text starts,
273
+ which nothing else fixes. */
274
+ --_qm-inset-control: var(--_qm-space) var(--_qm-space-3);
275
+ /* The scroll tail: dead space under the last card, so it can be read at the
276
+ middle of a pane rather than at its bottom edge. A fraction of the viewport
185
277
  rather than a step on the ramp, because what it answers is how far the eye
186
278
  sits from the bottom of the screen, which no spacing dial knows. Spent by
187
279
  the editor alone (`.qm-pane`): a stack of cards is what has a last one to
@@ -193,7 +285,7 @@
193
285
  floor is one number instead of one expression per control. */
194
286
  --_qm-tap-min: 24px;
195
287
  /* The reorder/delete glyph. A rung because the card header's
196
- right hang is HALF the difference between this and the tap floor: the glyph
288
+ right hang is half the difference between this and the tap floor: the glyph
197
289
  centres in a target sized by the floor, so what stands between it and the
198
290
  card's gutter is an arithmetic of the two, and a literal on either side is a
199
291
  balance that silently breaks when the glyph moves. Sized in CSS rather than
@@ -203,26 +295,49 @@
203
295
  --_qm-glyph-control: 14px;
204
296
 
205
297
  /* ── Type ────────────────────────────────────────────────────────────────
206
- `--qm-font-size` anchors the body rung; 1.125 is the ratio between adjacent
207
- rungs: a step up (title) and two down (label, meta). The ratio and the
208
- weights are fixed conventions, not dials: no consumer tunes a typographic
209
- ratio, and a dial nobody turns is a name every reader still holds. */
298
+ `--qm-font-size` anchors the chrome rung; 1.125 is the ratio between adjacent
299
+ rungs: a step up (title) and one down (label). The ratio and the
300
+ weight are fixed conventions, not dials: no consumer tunes a typographic
301
+ ratio, and a dial nobody turns is a name every reader still holds. Three chrome
302
+ rungs: a second step below `label` lands near 11px, under a size anything here
303
+ wants to be read at, and the supporting text that would take it is what `label`
304
+ already is. */
210
305
  --_qm-text-body: var(--qm-font-size, 0.875rem);
211
306
  --_qm-text-title: calc(var(--_qm-text-body) * 1.125);
212
307
  --_qm-text-label: calc(var(--_qm-text-body) / 1.125);
213
- --_qm-text-meta: calc(var(--_qm-text-label) / 1.125);
214
- --_qm-weight-label: 600;
215
- --_qm-weight-soft: 500;
308
+ /* Paper: the document body answers to the preview's paper where every other
309
+ surface here answers to the control beside it, so a rung shared with the chrome
310
+ makes a consumer asking for larger prose take larger inputs with it. 16/15 where
311
+ the ramp steps at 9/8: small enough to read as a change of register rather than
312
+ a rung of hierarchy, and clear of the title rung, which one 9/8 step lands on
313
+ exactly. */
314
+ --_qm-text-paper: calc(var(--_qm-text-body) * 16 / 15);
315
+ /* Two weights over the regular, and only one of them is chrome. `mid` names
316
+ everything the surface names — a card title, a section header, a field
317
+ label, a nested property — so a stack of them reads as one register and
318
+ hierarchy stays the size ramp's, which is the axis that can carry it.
319
+ 500 rather than 600 because the family dial resolves to the platform UI
320
+ face, whose 600 is heavier at the same number than the webfont a surface
321
+ is usually drawn against: at the label rung it goes smudgy, and one
322
+ number across three sizes does not read as one weight.
323
+
324
+ `strong` is not a rung of that ramp. It is the mark a run wears where
325
+ weight is the only signal it has: a heading inside a prose leaf, drawn at
326
+ no size step of its own because the quill sets the paper's; a table
327
+ island's header row; the stored value in an enum listbox. Reaching for it
328
+ where a size or a tone could speak is the drift the split exists to
329
+ stop. */
330
+ --_qm-weight-strong: 600;
331
+ --_qm-weight-mid: 500;
216
332
  /* Leading: the ramp's third axis, and it does not track the sizes one for one:
217
333
  two rungs, because a wrapped label and a wrapped paragraph want different
218
334
  rhythms and nothing wants four. `body` is reading rhythm (both prose leaves,
219
335
  every control beside them, the tips card's guidance); `tight` is a line that is
220
- a label rather than a passage. Unitless, so a rung inherits MULTIPLICATIVELY
336
+ a label rather than a passage. Unitless, so a rung inherits multiplicatively
221
337
  and holds against whichever size rung the surface reads: a `rem` leading
222
- would freeze at one size and disagree with the rest of the ramp. Leading is
223
- also the WHOLE of what separates paper from chrome: the body leaf reads
224
- `--_qm-text-body`, the same size as the input beside it, and there is no fifth
225
- size rung over it. `line-height: 1` is outside the axis: it collapses the
338
+ would freeze at one size and disagree with the rest of the ramp. It is the
339
+ axis paper and chrome share: both read `body`, and the size rung is what
340
+ separates them. `line-height: 1` is outside the axis: it collapses the
226
341
  line box onto its content, a glyph or a button's one-line label. */
227
342
  --_qm-leading-body: 1.5;
228
343
  --_qm-leading-tight: 1.3;
@@ -235,12 +350,12 @@
235
350
  picker, the caret) lands on the same pole the poles above resolve to.
236
351
  Declaring it here would break that lockstep in the one direction that
237
352
  matters: a host that declares `dark` would get light native chrome under
238
- a dark surface. A consumer that PINS a palette against its own scheme
353
+ a dark surface. A consumer that pins a palette against its own scheme
239
354
  declares `color-scheme` with it, on the root (THEMING.md).
240
355
 
241
- Face, ink and LEADING are the three axes a root carries this way. Leading
356
+ Face, ink and leading are the three axes a root carries this way. Leading
242
357
  belongs with the other two because a unitless ratio inherits
243
- MULTIPLICATIVELY: it holds against whatever size rung each surface reads, so
358
+ multiplicatively: it holds against whatever size rung each surface reads, so
244
359
  it imposes less than the ink beside it, and the reason `font-size` stays off
245
360
  does not transfer: a ratio has no size to sweep a consumer's content with.
246
361
  What it buys is that `normal` (the UA figure near 1.14, varying by font) is
@@ -248,13 +363,13 @@
248
363
  whether or not it remembers to say so, and a per-surface
249
364
  `--_qm-leading-tight` reads as the deliberate override it is.
250
365
 
251
- Deliberately NOT `font-size`, and the reason is about the ROOT rule rather
366
+ Deliberately not `font-size`, and the reason is about the root rule rather
252
367
  than about size: a root-level body size sweeps every descendant a consumer
253
368
  may have put inside the marker, which is a reach the derivation does not
254
369
  have. The rung is not withheld from the surfaces: every control and every
255
370
  prose leaf reads `--_qm-text-body` in its own recipe, which is
256
371
  what closes the ramp and makes `--qm-font-size` move an input. What would
257
- break a mounted leaf is a size that CHANGES under it; a rung is fixed at
372
+ break a mounted leaf is a size that changes under it; a rung is fixed at
258
373
  mount unless the consumer turns the dial, and a consumer turning a dial is
259
374
  entitled to the reflow. So: the root imposes no size, each surface reads the
260
375
  rung it wants. */
@@ -263,10 +378,10 @@
263
378
  line-height: var(--_qm-leading-body);
264
379
  }
265
380
 
266
- /* Reduced motion collapses the SCALE, never a list of selectors.
381
+ /* Reduced motion collapses the scale, never a list of selectors.
267
382
  Two values here are not the rung they look like:
268
383
 
269
- `1ms` rather than `none`, because a collapsed transition still FIRES: a surface
384
+ `1ms` rather than `none`, because a collapsed transition still fires: a surface
270
385
  waiting on `transitionend` keeps its event, where `transition: none` would strand
271
386
  it. The beat is under perception either way.
272
387
 
@@ -1,7 +1,7 @@
1
1
  /* `@quillmark/svelte/preset`, the package's one CSS entry point: the endorsed look for
2
2
  the page a surface is mounted on. Opt-in and side-effect free until imported:
3
3
  `core/index.ts` pulls `core/theme.css` for the surfaces and never this, because this
4
- styles the DOCUMENT and nothing may put that in a consumer's page uninvited.
4
+ styles the document and nothing may put that in a consumer's page uninvited.
5
5
 
6
6
  Two files because one of them is exempt: `scale.css` is where the literals are
7
7
  minted, `recipes.css` is held to reading them. */
@@ -1,14 +1,14 @@
1
- /* THE ENDORSED LOOK: the rules a host draws around a mounted surface AND the shell it
1
+ /* The endorsed look: the rules a host draws around a mounted surface and the shell it
2
2
  mounts one in, as classes it puts on its own elements. The rungs beside this in
3
3
  `scale.css` are the invisible half (a scale alone still leaves a consumer writing
4
4
  every declaration before anything on the page looks like anything), so the recipes
5
5
  are what the preset is for. This file mints nothing; `check:style` runs its axes here.
6
6
 
7
- These are HOST classes. They land on the consumer's DOM, never on a mounted root, so
7
+ These are host classes. They land on the consumer's DOM, never on a mounted root, so
8
8
  they freeze nothing about the surfaces' interior: the reason the package withholds
9
9
  its own internal classes does not reach them.
10
10
 
11
- UNLAYERED, deliberately. `@layer qm` exists so consumer CSS beats the surfaces
11
+ Unlayered, deliberately. `@layer qm` exists so consumer CSS beats the surfaces
12
12
  without `!important`, and the same has to be true here; a layer of the preset's own
13
13
  would put a second name in the contract and change what `revert-layer` at the foot
14
14
  of this file resolves against. Unlayered, a consumer's own rules win by ordinary
@@ -16,12 +16,12 @@
16
16
 
17
17
  /* ── The page ──────────────────────────────────────────────────────────────── */
18
18
 
19
- /* The baseline, on a CLASS rather than `body`: a preset that restyled the document
19
+ /* The baseline, on a class rather than `body`: a preset that restyled the document
20
20
  root would land on an app that already has a page and change it out from under
21
21
  whatever was there. The host puts this where its own layout starts: `body`, or the
22
22
  element the app mounts into.
23
23
 
24
- `color-scheme` is declared HERE and not by the package: light or dark is the
24
+ `color-scheme` is declared here and not by the package: light or dark is the
25
25
  mounting site's, and this file is the mounting site. `light dark` follows the OS;
26
26
  a host pinning one declares it on the same element and wins by source order. */
27
27
  .qm-page {
@@ -43,11 +43,11 @@
43
43
 
44
44
  /* ── The workspace ─────────────────────────────────────────────────────────── */
45
45
 
46
- /* The rules above dress the page a surface sits ON; these four are the SHELL it is
46
+ /* The rules above dress the page a surface sits on; these four are the shell it is
47
47
  mounted in, a tool where `.qm-page` is a document: the screen an app that mounts two
48
48
  surfaces over one session is, drawn once (ARCHITECTURE §Styling).
49
49
 
50
- PINNED to the viewport rather than sized to it: a page-height box still leaves the
50
+ Pinned to the viewport rather than sized to it: a page-height box still leaves the
51
51
  root scroller counting the overflow its panes clip, and a wheel over the chrome drags
52
52
  the whole app off the top. Three bands (a head, the body, an optional foot), and the
53
53
  body takes what is left and may shrink below its content, so the panes in it own
@@ -67,8 +67,8 @@ html:has(.qm-workspace) {
67
67
  overscroll-behavior: none;
68
68
  }
69
69
 
70
- /* A band's LINE: the row a head or a foot puts its parts on, wrapping, centred. That is
71
- the whole of it. What a band is made OF is the host's: the rule under it, the gutter
70
+ /* A band's line: the row a head or a foot puts its parts on, wrapping, centred. That is
71
+ the whole of it. What a band is made of is the host's: the rule under it, the gutter
72
72
  beside it, and the depth of it, because where a band ends and how tall it stands are
73
73
  page-shell decisions: a site holding its content to a maximum runs the rule past it at
74
74
  its own rhythm, and a one-screen tool has no maximum to hold to. */
@@ -79,18 +79,14 @@ html:has(.qm-workspace) {
79
79
  gap: var(--qmh-space-2) var(--qmh-space-4);
80
80
  }
81
81
 
82
- /* TWO EVEN TRACKS for two surfaces, and no resizer, since a divider that can be dragged
82
+ /* Two even tracks for two surfaces, and no resizer, since a divider that can be dragged
83
83
  is a control competing with what it divides. Each track may shrink below its content,
84
84
  which is what lets the frame in it clip rather than grow.
85
85
 
86
- The GAP is the endorsed default and not a fact about splitting: two surfaces standing
86
+ The gap is the endorsed default and not a fact about splitting: two surfaces standing
87
87
  apart on a page read as two things, which is what a host showing its instruments wants.
88
88
  A host that spends the whole screen on the mounts closes it to nothing and carries a
89
- hairline on one pane instead: one property, and the tracks are untouched.
90
-
91
- Below the width that fits two mounts abreast the split stops being one: the tracks
92
- stack and each takes a pane's height. WHICH element scrolls then is the host's, since
93
- it is the host that knows where the room comes from. */
89
+ hairline on one pane instead: one property, and the tracks are untouched. */
94
90
  .qm-split {
95
91
  display: grid;
96
92
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
@@ -99,26 +95,77 @@ html:has(.qm-workspace) {
99
95
  min-height: 0;
100
96
  }
101
97
 
102
- @media (width < 60rem) {
98
+ /* The band that switches them, and nothing at the widths where both tracks are showing
99
+ and there is nothing to switch. Where it stands is the host's, like any other band; what
100
+ goes in it is a pair of `.qm-control`s, which already carry the on-state a pressed
101
+ control takes. Its visibility is the preset's because the threshold is: a host hiding
102
+ its own tab row writes this file's number a second time, which is the one thing the
103
+ single source is for. */
104
+ .qm-switch {
105
+ display: none;
106
+ }
107
+
108
+ /* One mount at A time under the threshold, rather than two of them abreast. Half a narrow
109
+ viewport is under the width either surface reads at, so the split stops being one and
110
+ the tracks become views of the same room: the host says which with `data-qm-show`, and
111
+ the attribute names the track rather than what is in it, so nothing here knows an editor
112
+ from a preview. No attribute is the first track.
113
+
114
+ The other track is `display: none` and not a clip, which is the whole of the hiding: it
115
+ leaves the tab order, hit-testing and the accessibility tree in one property, so the
116
+ pane needs no `inert` and the host needs no breakpoint in JS. Both stay mounted (a
117
+ preview binds its handle once, an editor holds a caret, a history and a scroll), so
118
+ the switch is presentational, never conditional. A pane hidden this way keeps the
119
+ pixels it had (the paint loop's band sweep no-ops on an empty visible set) and repaints
120
+ from its live box when it comes back, which its resize observer sees as a width moving
121
+ off zero.
122
+
123
+ Height is the second clause because a landscape phone passes the first one: it clears
124
+ the width with room for two tracks and has no body left to put them in. A desktop window
125
+ that short wants one mount for the same reason. */
126
+ @media (width < 50rem), (height < 30rem) {
127
+ .qm-switch {
128
+ display: flex;
129
+ gap: var(--qmh-space);
130
+ }
131
+
132
+ /* Even halves of the band, at the height a thumb needs. The floor is the recipe's and
133
+ not the host's, because the band exists only at the sizes where the pointer is a
134
+ finger: a host is free to stand the band deeper, and has nothing to gain from
135
+ standing it shallower than the thing it is tapping. */
136
+ .qm-switch > * {
137
+ flex: 1 1 0;
138
+ min-height: var(--qmh-tap);
139
+ }
140
+
141
+ /* Centred, which is what a control filling half a band reads as; `.qm-control` itself
142
+ reads left, for the menus it also dresses. */
143
+ .qm-switch > .qm-control {
144
+ text-align: center;
145
+ }
146
+
103
147
  .qm-split {
104
148
  grid-template-columns: minmax(0, 1fr);
105
- grid-template-rows: none;
106
- grid-auto-rows: var(--qmh-pane);
149
+ }
150
+
151
+ .qm-split:not([data-qm-show='2']) > :nth-child(2),
152
+ .qm-split[data-qm-show='2'] > :nth-child(1) {
153
+ display: none;
107
154
  }
108
155
  }
109
156
 
110
- /* THE MOUNTING SITE: an edge, a corner, and the clip that holds a surface to them, which
157
+ /* The mounting site: an edge, a corner, and the clip that holds a surface to them, which
111
158
  is all a surface painting its own column needs. No tone and no gutter: those are the
112
159
  surface's own (THEMING §"Drop it in"). `border-box`, so a stated height is the height
113
160
  it draws.
114
161
 
115
- POSITIONED, because a clip reaches only what it is the containing block for: a
162
+ Positioned, because a clip reaches only what it is the containing block for: a
116
163
  surface's out-of-flow parts (a parked description, an overlay) resolve against the
117
164
  nearest positioned ancestor, and with none between them and the document they land at
118
- their static position UNCLIPPED, leaving the page to scroll past its own foot by
165
+ their static position unclipped, leaving the page to scroll past its own foot by
119
166
  however far the frame's contents run over.
120
167
 
121
- The two minimums are what let a frame in a track CLIP rather than push: a grid or flex
168
+ The two minimums are what let a frame in a track clip rather than push: a grid or flex
122
169
  item floors at its content, so a mount that paints wider than its column widens the
123
170
  column instead. They ride the frame rather than the split's children, which keeps every
124
171
  rule in this file landing where the host put a class. Outside a track they are the
@@ -161,7 +208,7 @@ html:has(.qm-workspace) {
161
208
  }
162
209
 
163
210
  /* The block form: a dump, or a snippet. `pre` keeps the wrapping the value came with;
164
- the plate is the page's own, one rung under whatever it sits in. How TALL such a
211
+ the plate is the page's own, one rung under whatever it sits in. How tall such a
165
212
  block may grow before it scrolls is the host's policy rather than the look, so no
166
213
  height is stated here. */
167
214
  pre.qm-readout {
@@ -206,10 +253,12 @@ pre.qm-readout {
206
253
 
207
254
  /* ── Surfaces ──────────────────────────────────────────────────────────────── */
208
255
 
209
- /* The instrument plate: what a host puts its controls and readouts on. One rung off the
210
- page and a hairline, which is the same way a card steps off the base surface, so a
211
- panel beside a mounted editor reads as the same material. `border-box`, so a height
212
- from the caller is the height it draws. */
256
+ /* The instrument plate: what a host puts its controls and readouts on. The same plane a
257
+ card is, so a panel beside a mounted editor reads as the same material. It keeps a
258
+ hairline where a card drops one: a card sits in a column the surface itself paints and
259
+ a plate sits wherever the host put it, which is the floating case's argument on a
260
+ block that happens to be in flow. `border-box`, so a height from the caller is the
261
+ height it draws. */
213
262
  .qm-panel {
214
263
  box-sizing: border-box;
215
264
  background: var(--qmh-surface);
@@ -220,7 +269,7 @@ pre.qm-readout {
220
269
 
221
270
  /* ── Controls ──────────────────────────────────────────────────────────────── */
222
271
 
223
- /* A host's controls ARE boxed, where the package's buttons are not: inside a card a
272
+ /* A host's controls are boxed, where the package's buttons are not: inside a card a
224
273
  button sits beside typing and a box would read as a field, and out here there is no
225
274
  typing to be confused with, so the box is what says "this is a control".
226
275
  `font: inherit` first, since the shorthand resets whatever precedes it. */
@@ -262,7 +311,7 @@ pre.qm-readout {
262
311
  /* ── Focus ─────────────────────────────────────────────────────────────────── */
263
312
 
264
313
  /* One visible indicator for the whole host, since none of these controls draws a ring
265
- of its own. It reads the INK rung rather than a hue: the page is monochrome, and a
314
+ of its own. It reads the ink rung rather than a hue: the page is monochrome, and a
266
315
  focus ring is a position, not a destination. */
267
316
  :focus-visible {
268
317
  outline: var(--qmh-ring-width) solid var(--qmh-ink);