monowind 0.2.13 → 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.
package/src/styles.css CHANGED
@@ -2,11 +2,17 @@
2
2
  * The shadow root has its own baked-in styles; this file targets the light-DOM
3
3
  * subtree the host owns.
4
4
  *
5
- * Layering: defaults and normalization that USERS may override live in
6
- * `@layer base` — Tailwind's utilities layer (and any unlayered author CSS)
7
- * then wins over them, matching Tailwind's own preflight behavior. Rules the
8
- * ENGINE owns (geometry, typography locking, alignment/overflow enforcement)
9
- * stay unlayered with `!important` so no utility can push content off-grid. */
5
+ * Layering, on Tailwind's layer names: defaults and normalization that
6
+ * USERS may override live in `@layer base` — Tailwind's utilities layer
7
+ * (and any unlayered author CSS) then wins over them, matching Tailwind's
8
+ * own preflight behavior. The locks the ENGINE owns (geometry, typography,
9
+ * the paint the grid takes over) live in `@layer theme`, the first layer,
10
+ * with `!important`: among important declarations the earliest layer
11
+ * wins, so they beat every later layer's, a utility's `!` modifier
12
+ * included, and any unlayered author `!important`; only an inline
13
+ * `style` marked important (element-attached) and a running transition
14
+ * still beat them. The plain engine rules stay unlayered, ahead of every
15
+ * layer's plain declarations. */
10
16
 
11
17
  /* Gap-decoration utilities + engine mirrors (specs/gap-decorations.md)
12
18
  * and the synthesized pointer-state variants — Tailwind source, split
@@ -16,6 +22,10 @@
16
22
  @import "./utilities.css";
17
23
  @import "./variants.css";
18
24
 
25
+ /* Tailwind's order for the two layers this file uses, so a page that
26
+ * loads the companion before Tailwind orders them the same way. */
27
+ @layer theme, base;
28
+
19
29
  @layer base {
20
30
  mono-wind {
21
31
  display: block;
@@ -35,10 +45,8 @@
35
45
  * them in Safari (faces with a real bold are unaffected); synthetic
36
46
  * oblique only slants, so style synthesis stays allowed. */
37
47
  font-synthesis: style;
38
- /* Themable fg / bg pair for the focus-invert default. System
39
- * colors honor the viewer's color scheme; authors override. */
40
- --mw-fg: canvastext;
41
- --mw-bg: canvas;
48
+ /* `--mw-fg`/`--mw-bg` are the engine's, derived from the host's
49
+ * colors into its shadow's `:host` rule (specs/theming.md). */
42
50
  /* ANSI-16 theme tokens (specs/theming.md): what SGR-colored ascii
43
51
  * art and effect filters resolve against, so a "red" follows the
44
52
  * theme. Defaults follow the VS Code terminal palette. */
@@ -136,23 +144,47 @@
136
144
  overflow: clip;
137
145
  }
138
146
 
147
+ /* A top-layer element is a surface of the host's, as the UA makes
148
+ * it one of the canvas's (specs/top-layer.md): the derived tokens in
149
+ * place of Canvas and CanvasText; `bg-clear` sees through. */
150
+ mono-wind :is([popover], dialog) {
151
+ color: var(--mw-fg);
152
+ background-color: var(--mw-bg);
153
+ }
154
+
139
155
  /* :focus-visible on ANY element: TUI-native invert. Outlines off
140
156
  * (they'd clip against grid glyphs). Where each declaration lands:
141
157
  * bg only shows via the grid (engine reads it during [measuring];
142
158
  * the light-DOM bg is transparent-locked below); color/caret-color
143
159
  * paint on editables (they're the ones exempted from the
144
160
  * color-transparent lock), and get grid-mirrored by the engine for
145
- * everyone else. */
161
+ * everyone else. A composite's container (a menu, a listbox) has its
162
+ * items for its focus indication; interactive.test.ts holds the list
163
+ * equal to COMPOSITE in element.ts. */
146
164
  mono-wind :focus-visible {
147
165
  outline: none;
166
+ }
167
+ mono-wind
168
+ :focus-visible:not(
169
+ [role="grid"],
170
+ [role="listbox"],
171
+ [role="menu"],
172
+ [role="menubar"],
173
+ [role="radiogroup"],
174
+ [role="tablist"],
175
+ [role="tree"],
176
+ [role="treegrid"]
177
+ ) {
148
178
  background-color: var(--mw-fg);
149
179
  color: var(--mw-bg);
150
180
  caret-color: var(--mw-bg);
151
181
  }
152
182
  /* Grid-mode light elements carry the grid's text cursor: Firefox
153
183
  * hit-tests a multicol spanner's anonymous wrapper as its container
154
- * despite `pointer-events: none`, and would show an arrow there.
155
- * Interactives keep their own (UA and authored cursors win). */
184
+ * despite `pointer-events: none`, and would show an arrow there. The
185
+ * interactives (specs/cell-model.md "Pointer states") keep their own;
186
+ * interactive.test.ts holds this list equal to INTERACTIVE in
187
+ * element.ts. */
156
188
  mono-wind[select="grid"]
157
189
  :is([data-mw-laid-out], [data-mw-flow], [data-mw-float]):not(
158
190
  a,
@@ -161,8 +193,30 @@
161
193
  select,
162
194
  textarea,
163
195
  label,
164
- [tabindex],
165
- [role="button"]
196
+ summary,
197
+ [contenteditable]:not([contenteditable="false"]),
198
+ [tabindex]:not(
199
+ [tabindex="-1"],
200
+ [role="grid"],
201
+ [role="listbox"],
202
+ [role="menu"],
203
+ [role="menubar"],
204
+ [role="radiogroup"],
205
+ [role="tablist"],
206
+ [role="tree"],
207
+ [role="treegrid"]
208
+ ),
209
+ [role="button"],
210
+ [role^="menuitem"],
211
+ [role="option"],
212
+ [role="tab"],
213
+ [role="treeitem"],
214
+ [role="checkbox"],
215
+ [role="radio"],
216
+ [role="switch"],
217
+ [role="slider"],
218
+ [role="link"],
219
+ [role="combobox"]
166
220
  ) {
167
221
  cursor: text;
168
222
  }
@@ -170,26 +224,61 @@
170
224
 
171
225
  /* === Modes and selection (engine-owned from here down) =============== */
172
226
 
173
- /* select="grid": pass pointer events through non-interactive light-DOM
174
- * elements so drag lands on the grid <pre> (native drag-selection of
175
- * the ASCII); interactives opt back into `auto` so clicks still work.
176
- * (The slot itself is pointer-events: none in the shadow CSS.) The
177
- * interactive list is mirrored by INTERACTIVE in element.ts and by the
178
- * two rules below change all of them together. */
227
+ /* select="grid": pointer events pass through the light DOM so a drag
228
+ * lands on the grid <pre>; the interactives opt back in for clicks,
229
+ * except during a grid drag, so the selection sweeps through their
230
+ * cells. The list repeats the cursor rule's: these rules must be
231
+ * important (a library writes inline pointer-events on its layers) and
232
+ * unlayered, out of the lock layer (an author's `pointer-events-none!`
233
+ * or `pointer-events-auto!` still wins), the cursor rule layered and
234
+ * plain (an author's cursor wins). interactive.test.ts holds the two
235
+ * lists equal to INTERACTIVE in element.ts. */
179
236
  mono-wind[select="grid"] * {
180
237
  pointer-events: none !important;
181
238
  }
182
- mono-wind[select="grid"]
183
- :is(a, button, input, select, textarea, label, [tabindex], [role="button"]) {
239
+ mono-wind[select="grid"]:not([data-mw-dragging])
240
+ :is(
241
+ a,
242
+ button,
243
+ input,
244
+ select,
245
+ textarea,
246
+ label,
247
+ summary,
248
+ [contenteditable]:not([contenteditable="false"]),
249
+ [tabindex]:not(
250
+ [tabindex="-1"],
251
+ [role="grid"],
252
+ [role="listbox"],
253
+ [role="menu"],
254
+ [role="menubar"],
255
+ [role="radiogroup"],
256
+ [role="tablist"],
257
+ [role="tree"],
258
+ [role="treegrid"]
259
+ ),
260
+ [role="button"],
261
+ [role^="menuitem"],
262
+ [role="option"],
263
+ [role="tab"],
264
+ [role="treeitem"],
265
+ [role="checkbox"],
266
+ [role="radio"],
267
+ [role="switch"],
268
+ [role="slider"],
269
+ [role="link"],
270
+ [role="combobox"]
271
+ ) {
184
272
  pointer-events: auto !important;
185
273
  }
186
- /* While a drag that began on the grid is in flight, interactives drop
187
- * their pointer events too, so the native selection sweeps through
188
- * their cells instead of stalling at their edge (a click is a press and
189
- * release in place — unaffected). */
190
- mono-wind[select="grid"][data-mw-dragging]
191
- :is(a, button, input, select, textarea, label, [tabindex], [role="button"]) {
192
- pointer-events: none !important;
274
+ /* A modal dialog blocks the page, the grid included (specs/top-layer.md
275
+ * deviation 7): inside it, grid mode falls back to the light DOM's own
276
+ * pointer events and selection, the highlight showing over the grid as
277
+ * in text mode. */
278
+ mono-wind[select="grid"] :is(dialog:modal, dialog:modal *) {
279
+ pointer-events: auto !important;
280
+ user-select: text;
281
+ -webkit-user-select: text;
193
282
  }
194
283
  /* The grid-mode selection lock, explicit on the light DOM: the shadow
195
284
  * slot's `user-select: none` reaches slotted content in Chromium and
@@ -227,11 +316,13 @@ mono-wind[select="grid"]:where(:not([data-mw-semantic-selection]))
227
316
  /* Scroll containers (specs/scrolling.md): the gutter ink IS the
228
317
  * scrollbar — native bars hidden. Measuring-gated so the authored
229
318
  * scrollbar-width/-color stay readable during the style pass. */
230
- mono-wind:not([measuring]) [data-mw-scroll] {
231
- scrollbar-width: none !important;
232
- }
233
- mono-wind:not([measuring]) [data-mw-scroll]::-webkit-scrollbar {
234
- display: none;
319
+ @layer theme {
320
+ mono-wind:not([measuring]) [data-mw-scroll] {
321
+ scrollbar-width: none !important;
322
+ }
323
+ mono-wind:not([measuring]) [data-mw-scroll]::-webkit-scrollbar {
324
+ display: none !important;
325
+ }
235
326
  }
236
327
  /* The scroll-range spacer: pins the native scrollable overflow to the
237
328
  * ENGINE's range (--mw-se-* = max + box, written by render.ts) so
@@ -260,16 +351,20 @@ mono-wind:not([measuring]) [data-mw-scroll]::after {
260
351
  * width, so a shrinking container still shrinks the host natively;
261
352
  * !important because the cap never exceeds the natural width it was
262
353
  * measured from, whatever max-width the author set (inline included). */
263
- mono-wind:not([measuring]) {
264
- max-width: var(--mw-host-w) !important;
354
+ @layer theme {
355
+ mono-wind:not([measuring]) {
356
+ max-width: var(--mw-host-w) !important;
357
+ }
265
358
  }
266
359
 
267
360
  /* Hide the host until the first layout completes to avoid the flash of raw
268
361
  * browser-rendered content (flex without our decorations, elements at their
269
362
  * natural sizes). `visibility: hidden` preserves layout & measurability so
270
363
  * cell metrics can still be probed while invisible. */
271
- mono-wind:not([data-mw-ready]) {
272
- visibility: hidden !important;
364
+ @layer theme {
365
+ mono-wind:not([data-mw-ready]) {
366
+ visibility: hidden !important;
367
+ }
273
368
  }
274
369
 
275
370
  /* A light-DOM selection is painted on the grid — inverted cells from the
@@ -282,12 +377,14 @@ mono-wind:not([data-mw-ready]) {
282
377
  * paint selections themselves and strip backgrounds, so the native
283
378
  * highlight stays there. */
284
379
  @media (forced-colors: none) {
285
- mono-wind:not([measuring])[data-mw-leaf]::selection,
286
- mono-wind:not([measuring])
287
- :where(:not(input, textarea, select, [contenteditable], [contenteditable] *))::selection {
288
- color: transparent !important;
289
- text-shadow: none !important;
290
- background: transparent !important;
380
+ @layer theme {
381
+ mono-wind:not([measuring])[data-mw-leaf]::selection,
382
+ mono-wind:not([measuring])
383
+ :where(:not(input, textarea, select, [contenteditable], [contenteditable] *))::selection {
384
+ color: transparent !important;
385
+ text-shadow: none !important;
386
+ background: transparent !important;
387
+ }
291
388
  }
292
389
  /* An editable renders its own text, and Chromium and WebKit hand it
293
390
  * its parent's `::selection` (css-pseudo-4 highlight inheritance —
@@ -303,486 +400,547 @@ mono-wind:not([data-mw-ready]) {
303
400
 
304
401
  /* === Typography locks and measuring gates ============================ */
305
402
 
306
- /* Lock the font on descendants so the grid metrics stay consistent —
307
- * `!important` because user classes (`.text-2xl` etc.) would otherwise beat
308
- * these element-selector rules on specificity. */
309
- mono-wind * {
310
- font-family: inherit !important;
311
- font-size: inherit !important;
312
- }
403
+ @layer theme {
404
+ /* Lock the font on descendants so the grid metrics stay consistent —
405
+ * `!important` because user classes (`.text-2xl` etc.) would otherwise beat
406
+ * these element-selector rules on specificity. */
407
+ mono-wind * {
408
+ font-family: inherit !important;
409
+ font-size: inherit !important;
410
+ }
313
411
 
314
- /* Lock toggles must never become native animations: an authored
315
- * `transition` would otherwise ANIMATE them — a background flashes to
316
- * its real value while `[measuring]` lifts the lock, and the snap back
317
- * to transparent starts a native fade that paints the light-DOM
318
- * element ON TOP of the grid (transitions beat even `!important`
319
- * declarations in the cascade). Restricting `transition-property` to
320
- * the SAMPLED properties (which no lock toggles — color stays live
321
- * under the text-fill lock, and nothing touches border colors or
322
- * opacity) snaps everything lock-owned while keeping in-flight fades
323
- * of the sampled properties alive ACROSS measuring passes. The engine
324
- * swaps `[measuring]` for `[settling]` and forces one style flush
325
- * before unmasking, so the snap-back delta always commits masked;
326
- * outside both attributes the authored list is live and fully
327
- * respected background fades, which can never run natively, are
328
- * synthesized by the engine instead. */
329
- mono-wind[measuring],
330
- mono-wind[measuring] *,
331
- mono-wind[settling],
332
- mono-wind[settling] * {
333
- transition-property: color, border-color, opacity !important;
334
- }
412
+ /* Lock toggles must never become native animations: an authored
413
+ * `transition` would otherwise ANIMATE them — a background flashes to
414
+ * its real value while `[measuring]` lifts the lock, and the snap back
415
+ * to transparent starts a native fade that paints the light-DOM
416
+ * element ON TOP of the grid (transitions beat even `!important`
417
+ * declarations in the cascade). Restricting `transition-property` to
418
+ * the SAMPLED properties (which no lock toggles — color stays live
419
+ * under the text-fill lock, and nothing touches border colors,
420
+ * opacity, or a layer's transforms and filter) snaps everything
421
+ * lock-owned (backgrounds, geometry, backdrop-filter) while keeping
422
+ * in-flight fades
423
+ * of the sampled properties alive ACROSS measuring passes. The engine
424
+ * swaps `[measuring]` for `[settling]` and forces one style flush
425
+ * before unmasking, so the snap-back delta always commits masked;
426
+ * outside both attributes the authored list is live and fully
427
+ * respected — background fades, which can never run natively, are
428
+ * synthesized by the engine instead. */
429
+ mono-wind[measuring],
430
+ mono-wind[measuring] *,
431
+ mono-wind[settling],
432
+ mono-wind[settling] * {
433
+ transition-property:
434
+ color, border-color, opacity, transform, translate, rotate, scale, filter !important;
435
+ }
335
436
 
336
- /* Grid-template read assist (specs/grid.md), no-Typed-OM engines only
337
- * (Firefox pre-157): getComputedStyle on a live grid container returns
338
- * the USED `grid-template-*` track list (expanded px) the authored
339
- * fr/repeat/minmax structure is gone. The style reader sets this
340
- * attribute for the duration of the read; blockifying the element makes
341
- * getComputedStyle return the COMPUTED value, structure intact.
342
- * `!important` so inline `style="display: grid"` can't defeat the read. */
343
- mono-wind[measuring] [data-mw-degrid] {
344
- display: block !important;
345
- }
437
+ /* Anchor names scoped to their own subtree while the engine reads: an
438
+ * anchored box then resolves no anchor natively, so the browser applies
439
+ * no position-try fallback of its own (from its pixel geometry, not the
440
+ * cells') and `position-area` reads as authored
441
+ * (specs/anchor-positioning.md). */
442
+ mono-wind[measuring] * {
443
+ anchor-scope: all !important;
444
+ }
346
445
 
347
- /* Grid-quantized typography and the wrap model (specs/cell-model.md). Gated
348
- * on `measuring` (unlike the font lock above) so the style reader sees the
349
- * AUTHORED white-space / letter-spacing / line-heightthat's how nowrap,
350
- * tracking, and leading are detected without echoing our own output.
351
- *
352
- * letter-spacing: the root's own (`--mw-rls`, part of the cell) plus
353
- * `--mw-ls` extra cells per character, which the engine writes on every
354
- * element it visits; an element without the var makes this declaration
355
- * invalid at computed-value time, so the property falls back to inheriting
356
- * the parent's grid-exact value. line-height is inherited from the
357
- * laid-out ancestor (which sets the row multiple below) — `leading-*` on
358
- * inline elements is ignored by design. */
359
- mono-wind:not([measuring]) * {
360
- /* Baseline-shift lock (cell-model): gated like the rest, so the style
361
- * reader sees the authored/UA vertical-align (table cells read it). */
362
- vertical-align: baseline !important;
363
- white-space: normal !important;
364
- overflow-wrap: anywhere !important;
365
- letter-spacing: calc(var(--mw-rls, 0px) + var(--mw-ls) * var(--mw-cw, 1px)) !important;
366
- line-height: inherit !important;
367
- /* Backgrounds paint in the grid too, so the light DOM's own bg goes
368
- * transparent — otherwise it would cover the grid (light DOM sits
369
- * ON TOP of the grid in the shadow tree so native selection
370
- * highlights can show through). The engine still reads authored
371
- * background-color during [measuring] (this rule is off then), so
372
- * bg-* and focus-invert both make it into the grid paint. */
373
- background-color: transparent !important;
374
- /* Gradients paint in the grid as a color per cell (specs/gradients.md),
375
- * read during [measuring] like the color. */
376
- background-image: none !important;
377
- text-shadow: none !important;
378
- /* Shadows paint in the grid as shades (specs/box-shadow.md), read
379
- * during [measuring] like backgrounds. */
380
- box-shadow: none !important;
381
- /* Inline-element horizontal padding, quantized: the engine writes
382
- * `--mw-ipl`/`--mw-ipr` (whole cells) on inline elements whose authored
383
- * padding it reserved in the run; everything else gets 0, so raw
384
- * off-grid padding never renders. Laid-out elements and atomic inline
385
- * boxes override this with their own engine-owned padding rule (higher
386
- * specificity). Vertical inline padding never moves layout, per CSS,
387
- * and passes through untouched. */
388
- padding-left: calc(var(--mw-ipl, 0) * var(--mw-cw, 1px)) !important;
389
- padding-right: calc(var(--mw-ipr, 0) * var(--mw-cw, 1px)) !important;
390
- }
446
+ /* A popover's or dialog's exit rides a discrete display transition
447
+ * (specs/top-layer.md): on them, and them alone, the mask keeps
448
+ * display and overlay alive across the passes no lock toggles either
449
+ * there, while the grid-template read assist below toggles display. */
450
+ mono-wind[measuring] :is([popover], dialog),
451
+ mono-wind[settling] :is([popover], dialog) {
452
+ transition-property:
453
+ color, border-color, opacity, transform, translate, rotate, scale, filter, display, overlay !important;
454
+ }
391
455
 
392
- /* Light-DOM text goes invisible so the grid is the only visible copy;
393
- * form controls opt out because they render their value/caret/selection
394
- * natively (the tree builder skips their content so the grid doesn't
395
- * double-render). Events, ARIA, focus, selection all still work.
396
- * `-webkit-text-fill-color` (universal support), NOT `color`: it hides
397
- * the ink while computed `color` stays LIVE — authored transitions run
398
- * on it for real and the engine samples true mid-fade values (the
399
- * animation loop, element.ts). Decoration ink follows `color`, so it
400
- * needs its own transparent lock (the grid paints decorations).
401
- * `[data-mw-leaf]` is the host with its own text on the grid — the root
402
- * leaf, or a mixed host's anonymous runs (specs/host-leaf.md): every
403
- * rule that shapes a leaf's native text carries this host variant —
404
- * render.ts writes the host's flags. */
405
- mono-wind:not([measuring])[data-mw-leaf],
406
- mono-wind:not([measuring]) *:not(input):not(textarea):not(select) {
407
- -webkit-text-fill-color: transparent !important;
408
- text-decoration-color: transparent !important;
409
- }
456
+ /* Grid-template read assist (specs/grid.md), no-Typed-OM engines only
457
+ * (Firefox pre-157): getComputedStyle on a live grid container returns
458
+ * the USED `grid-template-*` track list (expanded px) the authored
459
+ * fr/repeat/minmax structure is gone. The style reader sets this
460
+ * attribute for the duration of the read; blockifying the element makes
461
+ * getComputedStyle return the COMPUTED value, structure intact.
462
+ * `!important` so inline `style="display: grid"` can't defeat the read. */
463
+ mono-wind[measuring] [data-mw-degrid] {
464
+ display: block !important;
465
+ }
410
466
 
411
- /* Form controls escape the fill lock by INHERITANCE, not just by the
412
- * exclusion above: text-fill-color inherits and has no UA default on
413
- * controls (unlike `color`), so without this reset an input under any
414
- * locked ancestor would inherit transparent ink. */
415
- mono-wind:not([measuring]) input,
416
- mono-wind:not([measuring]) textarea,
417
- mono-wind:not([measuring]) select {
418
- -webkit-text-fill-color: currentColor !important;
419
- }
467
+ /* Grid-quantized typography and the wrap model (specs/cell-model.md). Gated
468
+ * on `measuring` (unlike the font lock above) so the style reader sees the
469
+ * AUTHORED white-space / letter-spacing / line-height that's how nowrap,
470
+ * tracking, and leading are detected without echoing our own output.
471
+ *
472
+ * letter-spacing: the root's own (`--mw-rls`, part of the cell) plus
473
+ * `--mw-ls` extra cells per character, which the engine writes on every
474
+ * element it visits; an element without the var makes this declaration
475
+ * invalid at computed-value time, so the property falls back to inheriting
476
+ * the parent's grid-exact value. line-height is inherited from the
477
+ * laid-out ancestor (which sets the row multiple below) — `leading-*` on
478
+ * inline elements is ignored by design. */
479
+ mono-wind:not([measuring]) * {
480
+ /* Baseline-shift lock (cell-model): gated like the rest, so the style
481
+ * reader sees the authored/UA vertical-align (table cells read it). */
482
+ vertical-align: baseline !important;
483
+ white-space: normal !important;
484
+ overflow-wrap: anywhere !important;
485
+ letter-spacing: calc(var(--mw-rls, 0px) + var(--mw-ls) * var(--mw-cw, 1px)) !important;
486
+ line-height: inherit !important;
487
+ /* Backgrounds paint in the grid too, so the light DOM's own bg goes
488
+ * transparent — otherwise it would cover the grid (light DOM sits
489
+ * ON TOP of the grid in the shadow tree so native selection
490
+ * highlights can show through). The engine still reads authored
491
+ * background-color during [measuring] (this rule is off then), so
492
+ * bg-* and focus-invert both make it into the grid paint. */
493
+ background-color: transparent !important;
494
+ /* Gradients paint in the grid as a color per cell (specs/gradients.md),
495
+ * read during [measuring] like the color. */
496
+ background-image: none !important;
497
+ text-shadow: none !important;
498
+ /* Shadows paint in the grid as shades (specs/box-shadow.md), read
499
+ * during [measuring] like backgrounds. */
500
+ box-shadow: none !important;
501
+ /* Inline-element horizontal padding, quantized: the engine writes
502
+ * `--mw-ipl`/`--mw-ipr` (whole cells) on inline elements whose authored
503
+ * padding it reserved in the run; everything else gets 0, so raw
504
+ * off-grid padding never renders. Laid-out elements and atomic inline
505
+ * boxes override this with their own engine-owned padding rule (higher
506
+ * specificity). Vertical inline padding never moves layout, per CSS,
507
+ * and passes through untouched. */
508
+ padding-left: calc(var(--mw-ipl, 0) * var(--mw-cw, 1px)) !important;
509
+ padding-right: calc(var(--mw-ipr, 0) * var(--mw-cw, 1px)) !important;
510
+ }
420
511
 
421
- /* Author-requested `white-space: nowrap` (or `pre`) the engine sized this
422
- * box for unwrapped hard lines, so the browser must not wrap either. Wins
423
- * over the lock above on specificity. */
424
- mono-wind:not([measuring])[data-mw-nowrap],
425
- mono-wind:not([measuring]) [data-mw-nowrap] {
426
- white-space: nowrap !important;
427
- }
512
+ /* A top-layer element's backdrop is a box of the grid's beneath its
513
+ * own (specs/top-layer.md), read during [measuring] like backgrounds. */
514
+ mono-wind:not([measuring]) [popover]::backdrop,
515
+ mono-wind:not([measuring]) dialog::backdrop {
516
+ background: transparent !important;
517
+ backdrop-filter: none !important;
518
+ }
428
519
 
429
- /* `white-space: pre` leaves (specs/cell-model.md): the tree builder kept
430
- * the source's spaces and newlines in the run, so the browser must render
431
- * them too on the leaf AND its inline descendants (the lock above would
432
- * collapse text inside nested spans). Later than the nowrap rule, so it
433
- * wins on the leaf itself at equal specificity. */
434
- mono-wind:not([measuring])[data-mw-pre],
435
- mono-wind:not([measuring])[data-mw-pre] *,
436
- mono-wind:not([measuring]) [data-mw-pre],
437
- mono-wind:not([measuring]) [data-mw-pre] * {
438
- white-space: pre !important;
439
- }
520
+ /* Light-DOM text goes invisible so the grid is the only visible copy;
521
+ * form controls opt out because they render their value/caret/selection
522
+ * natively (the tree builder skips their content so the grid doesn't
523
+ * double-render). Events, ARIA, focus, selection all still work.
524
+ * `-webkit-text-fill-color` (universal support), NOT `color`: it hides
525
+ * the ink while computed `color` stays LIVE — authored transitions run
526
+ * on it for real and the engine samples true mid-fade values (the
527
+ * animation loop, element.ts). Decoration ink follows `color`, so it
528
+ * needs its own transparent lock (the grid paints decorations).
529
+ * `[data-mw-leaf]` is the host with its own text on the grid — the root
530
+ * leaf, or a mixed host's anonymous runs (specs/host-leaf.md): every
531
+ * rule that shapes a leaf's native text carries this host variant —
532
+ * render.ts writes the host's flags. */
533
+ mono-wind:not([measuring])[data-mw-leaf],
534
+ mono-wind:not([measuring]) *:not(input):not(textarea):not(select) {
535
+ -webkit-text-fill-color: transparent !important;
536
+ text-decoration-color: transparent !important;
537
+ }
440
538
 
441
- /* Truncation (`truncate` = nowrap + clip + text-overflow: ellipsis): use
442
- * `hidden` rather than the normalized `clip` for this combo — some engines
443
- * only apply `text-overflow` to scroll containers, which `clip` boxes are
444
- * not. Wins over the `[data-mw-clip]` rule below on specificity. */
445
- mono-wind:not([measuring]) [data-mw-nowrap][data-mw-clip] {
446
- overflow: hidden !important;
447
- }
539
+ /* Form controls escape the fill lock by INHERITANCE, not just by the
540
+ * exclusion above: text-fill-color inherits and has no UA default on
541
+ * controls (unlike `color`), so without this reset an input under any
542
+ * locked ancestor would inherit transparent ink. */
543
+ mono-wind:not([measuring]) input,
544
+ mono-wind:not([measuring]) textarea,
545
+ mono-wind:not([measuring]) select {
546
+ -webkit-text-fill-color: currentColor !important;
547
+ }
448
548
 
449
- /* === Engine-owned geometry =========================================== */
549
+ /* Author-requested `white-space: nowrap` (or `pre`) — the engine sized this
550
+ * box for unwrapped hard lines, so the browser must not wrap either. Wins
551
+ * over the lock above on specificity. */
552
+ mono-wind:not([measuring])[data-mw-nowrap],
553
+ mono-wind:not([measuring]) [data-mw-nowrap] {
554
+ white-space: nowrap !important;
555
+ }
450
556
 
451
- /* Every CSS property the engine has taken over on laid-out elements.
452
- * Gated on the `measuring` attribute so the style reader still sees the
453
- * authored values during its read pass. Being unlayered already beats all
454
- * layered CSS (e.g. Tailwind utilities); the `!important` additionally
455
- * defends against inline `style=""` attributes (hand-written or CSS-in-JS)
456
- * and high-specificity unlayered author CSS. Known escape hatch: Tailwind's
457
- * `!` modifier (important-in-layer) still wins, per the cascade spec.
458
- *
459
- * Geometry: position/box-sizing/left/top/width/height + min/max — set from
460
- * the engine-owned --mw-x/y/w/h custom properties.
461
- *
462
- * Neutralized: border-width (painted as glyphs by the shadow grid),
463
- * margin (position:absolute + margin still nudges the element off-grid).
464
- *
465
- * Owned via `--mw-p{t,r,b,l}` (padding) and `--mw-b{t,r,b,l}` (border cells):
466
- * both must land on whole cells. The browser applies the sum as native
467
- * padding since we've zeroed native border-width — this way, content sits
468
- * inset by exactly the (border + padding) cells the engine planned for.
469
- *
470
- * Left alone: flex/grid/gap/justify/align — laid-out children are all
471
- * position:absolute (out of flow), so nothing consumes them anyway. */
472
- mono-wind:not([measuring]) [data-mw-laid-out] {
473
- position: absolute !important;
474
- /* A sticky box's shift for the scroll rides beside its position
475
- * (specs/sticky.md). */
476
- left: calc((var(--mw-x, 0) + var(--mw-sx, 0)) * var(--mw-cw, 1px)) !important;
477
- top: calc((var(--mw-y, 0) + var(--mw-sy, 0)) * var(--mw-ch, 1px)) !important;
478
- /* Neutralize the browser's residual grid machinery (specs/grid.md).
479
- * grid-area: per CSS §10.1 an ABSOLUTE child of a grid container with
480
- * placement properties uses its grid area as containing block — our
481
- * left/top would resolve against that area and double-offset the
482
- * element (the engine already placed it). The template and content/item
483
- * alignment resets make a laid-out container's own grid/flex inert for
484
- * the one box the browser still lays out: the anonymous item holding a
485
- * leaf's direct text (a template would trap it in track 1; the
486
- * browser's place-* offsets are FRACTIONAL, off the cell grid — the
487
- * engine applies the quantized offsets through its owned padding
488
- * instead, specs/cell-model.md). */
489
- grid-area: auto !important;
490
- grid-template-columns: none !important;
491
- grid-template-rows: none !important;
492
- place-content: start !important;
493
- place-items: stretch !important;
494
- /* `leading-*` on the grid: `--mw-lh` rows per wrapped line. CSS puts half
495
- * the extra leading ABOVE the first line; `--mw-lhs` (−(rows − 1)/2)
496
- * shifts the box back so every line starts on its own row. */
497
- translate: 0 calc(var(--mw-lhs, 0) * var(--mw-ch, 1px)) !important;
498
- }
557
+ /* `white-space: pre` leaves (specs/cell-model.md): the tree builder kept
558
+ * the source's spaces and newlines in the run, so the browser must render
559
+ * them too on the leaf AND its inline descendants (the lock above would
560
+ * collapse text inside nested spans). Later than the nowrap rule, so it
561
+ * wins on the leaf itself at equal specificity. */
562
+ mono-wind:not([measuring])[data-mw-pre],
563
+ mono-wind:not([measuring])[data-mw-pre] *,
564
+ mono-wind:not([measuring]) [data-mw-pre],
565
+ mono-wind:not([measuring]) [data-mw-pre] * {
566
+ white-space: pre !important;
567
+ }
499
568
 
500
- /* Shared cell geometry for engine-sized boxes engine-POSITIONED
501
- * elements, in-flow atomic inline boxes, and in-flow multicol spanners
502
- * (below). */
503
- mono-wind:not([measuring]) [data-mw-laid-out],
504
- mono-wind:not([measuring]) [data-mw-inline-box],
505
- mono-wind:not([measuring]) [data-mw-multicol-flow-span],
506
- mono-wind:not([measuring]) [data-mw-flow],
507
- mono-wind:not([measuring]) [data-mw-float] {
508
- box-sizing: border-box !important;
509
- /* A sticky shift (specs/sticky.md) belongs to the box the engine wrote
510
- * it on: custom properties inherit, so every box resets them and only
511
- * the engine's inline value — which beats an unimportant rule — moves
512
- * a box. */
513
- --mw-sx: 0;
514
- --mw-sy: 0;
515
- /* Trailing-gap allowance: a tracked box's lines end with a phantom
516
- * `--mw-ls`-cell gap the engine doesn't count. Give the content box that
517
- * room by carving it out of the engine-owned right padding/border cells;
518
- * only when those are fewer than the gap does the element box itself
519
- * widen (invisibly — the decorated border stays where the engine put it).
520
- * The extra 1/32px is one layout unit of headroom: engines store lengths
521
- * on a fixed grid by flooring (1/64px in Chromium and WebKit, 1/60px in
522
- * Firefox), so `cells × cw` can land one unit BELOW the shaped advance of
523
- * a line that fits exactly, and the browser would wrap it. Adding at
524
- * least one unit before the floor guarantees the stored width is ≥ the
525
- * exact product; 1/32px covers every engine's unit and is far below a
526
- * cell (and below a device pixel), so it changes neither cell math nor
527
- * paint. */
528
- width: calc(
529
- (var(--mw-w, 0) + max(0, var(--mw-ls, 0) - var(--mw-pr, 0) - var(--mw-br, 0))) *
530
- var(--mw-cw, 1px) + 0.03125px
531
- ) !important;
532
- height: calc(var(--mw-h, 0) * var(--mw-ch, 1px)) !important;
533
- min-width: 0 !important;
534
- min-height: 0 !important;
535
- max-width: none !important;
536
- max-height: none !important;
537
- border-width: 0 !important;
538
- /* Gap rules are engine-painted glyphs (specs/gap-decorations.md);
539
- * css-gaps browsers must not also paint their native (fractional)
540
- * rules — e.g. between an emptied grid's explicit tracks. */
541
- column-rule-style: none !important;
542
- row-rule-style: none !important;
543
- /* Authored z-index applies only where CSS says (positioned, flex/grid
544
- * items) — absolutization would otherwise activate it everywhere. The
545
- * engine writes --mw-z in exactly those cases; without it the calc()
546
- * is invalid at computed-value time and z-index falls back to auto. */
547
- z-index: calc(var(--mw-z)) !important;
548
- padding-top: calc((var(--mw-pt, 0) + var(--mw-bt, 0)) * var(--mw-ch, 1px)) !important;
549
- padding-right: calc(
550
- max(0, var(--mw-pr, 0) + var(--mw-br, 0) - var(--mw-ls, 0)) * var(--mw-cw, 1px)
551
- ) !important;
552
- padding-bottom: calc((var(--mw-pb, 0) + var(--mw-bb, 0)) * var(--mw-ch, 1px)) !important;
553
- padding-left: calc((var(--mw-pl, 0) + var(--mw-bl, 0)) * var(--mw-cw, 1px)) !important;
554
- margin: 0 !important;
555
- line-height: calc(var(--mw-lh, 1) * var(--mw-ch, 1px)) !important;
556
- }
569
+ /* Truncation (`truncate` = nowrap + clip + text-overflow: ellipsis): use
570
+ * `hidden` rather than the normalized `clip` for this combo — some engines
571
+ * only apply `text-overflow` to scroll containers, which `clip` boxes are
572
+ * not. Wins over the `[data-mw-clip]` rule below on specificity. */
573
+ mono-wind:not([measuring]) [data-mw-nowrap][data-mw-clip] {
574
+ overflow: hidden !important;
575
+ }
557
576
 
558
- /* Atomic inline boxes (`inline-flex`/`inline-block`/`inline-grid` riding a
559
- * text run, specs/cell-model.md): the box stays IN FLOW — the browser's
560
- * own line layout places it, and both models agree because the engine
561
- * sizes it to exactly the cells its U+FFFC marker occupies in the wrap.
562
- * `position: relative` makes it the containing block for its laid-out
563
- * children; `vertical-align: top` pins the box's top to its line's first
564
- * row (the global baseline lock would hang an abspos-only box below the
565
- * line) a box taller than one row grows its line, per CSS. */
566
- mono-wind:not([measuring]) [data-mw-inline-box] {
567
- position: relative !important;
568
- /* A sticky inline box's shift for the scroll (specs/sticky.md); the
569
- * vars are absent otherwise. */
570
- top: calc(var(--mw-sy, 0) * var(--mw-ch, 1px)) !important;
571
- left: calc(var(--mw-sx, 0) * var(--mw-cw, 1px)) !important;
572
- vertical-align: top !important;
573
- /* Same half-leading cancellation as laid-out elements — without it
574
- * an inline box's NATIVE ink (a textarea's value under leading-*)
575
- * sits centered in its line boxes instead of on its rows. */
576
- translate: 0 calc(var(--mw-lhs, 0) * var(--mw-ch, 1px)) !important;
577
- }
577
+ /* === Engine-owned geometry =========================================== */
578
+
579
+ /* Every CSS property the engine has taken over on laid-out elements.
580
+ * Gated on the `measuring` attribute so the style reader still sees the
581
+ * authored values during its read pass. In the lock layer (the file's
582
+ * header) the `!important` beats every utility, `!` modifier included,
583
+ * inline `style=""` attributes (hand-written or CSS-in-JS), and
584
+ * high-specificity unlayered author CSS.
585
+ *
586
+ * Geometry: position/box-sizing/left/top/width/height + min/max — set from
587
+ * the engine-owned --mw-x/y/w/h custom properties.
588
+ *
589
+ * Neutralized: border-width (painted as glyphs by the shadow grid),
590
+ * margin (position:absolute + margin still nudges the element off-grid).
591
+ *
592
+ * Owned via `--mw-p{t,r,b,l}` (padding) and `--mw-b{t,r,b,l}` (border cells):
593
+ * both must land on whole cells. The browser applies the sum as native
594
+ * padding since we've zeroed native border-width this way, content sits
595
+ * inset by exactly the (border + padding) cells the engine planned for.
596
+ *
597
+ * Left alone: flex/grid/gap/justify/align — laid-out children are all
598
+ * position:absolute (out of flow), so nothing consumes them anyway. */
599
+ mono-wind:not([measuring]) [data-mw-laid-out] {
600
+ position: absolute !important;
601
+ /* An anchored box is the engine's to place (specs/anchor-positioning.md):
602
+ * the browser's own anchored placement stays off. */
603
+ position-area: none !important;
604
+ /* A sticky box's shift for the scroll rides beside its position
605
+ * (specs/sticky.md). */
606
+ left: calc((var(--mw-x, 0) + var(--mw-sx, 0)) * var(--mw-cw, 1px)) !important;
607
+ /* `leading-*` on the grid: `--mw-lh` rows per wrapped line. CSS puts half
608
+ * the extra leading ABOVE the first line; `--mw-lhs` (−(rows − 1)/2)
609
+ * lifts the box back so every line starts on its own row. */
610
+ top: calc((var(--mw-y, 0) + var(--mw-sy, 0) + var(--mw-lhs, 0)) * var(--mw-ch, 1px)) !important;
611
+ /* Neutralize the browser's residual grid machinery (specs/grid.md).
612
+ * grid-area: per CSS §10.1 an ABSOLUTE child of a grid container with
613
+ * placement properties uses its grid area as containing block — our
614
+ * left/top would resolve against that area and double-offset the
615
+ * element (the engine already placed it). The template and content/item
616
+ * alignment resets make a laid-out container's own grid/flex inert for
617
+ * the one box the browser still lays out: the anonymous item holding a
618
+ * leaf's direct text (a template would trap it in track 1; the
619
+ * browser's place-* offsets are FRACTIONAL, off the cell grid — the
620
+ * engine applies the quantized offsets through its owned padding
621
+ * instead, specs/cell-model.md). */
622
+ grid-area: auto !important;
623
+ grid-template-columns: none !important;
624
+ grid-template-rows: none !important;
625
+ place-content: start !important;
626
+ place-items: stretch !important;
627
+ }
578
628
 
579
- /* Authored `vertical-align: bottom` on an atomic inline box: grid-exact
580
- * in every engine (probed), so it passes through; the engine drops the
581
- * line's text to the box's last row to match. middle/baseline are
582
- * off-grid and behave as top (cell-model deviation). */
583
- mono-wind:not([measuring]) [data-mw-inline-box][data-mw-vbottom] {
584
- vertical-align: bottom !important;
585
- }
629
+ /* A top-layer element's box is the viewport's (specs/top-layer.md):
630
+ * its cells count from the grid's client origin, a `:host` rule of the
631
+ * shadow's in px, kept current through page scrolls. */
632
+ mono-wind:not([measuring]) [data-mw-laid-out][data-mw-top] {
633
+ position: fixed !important;
634
+ left: calc(
635
+ var(--mw-ox, 0px) + (var(--mw-x, 0) + var(--mw-sx, 0)) * var(--mw-cw, 1px)
636
+ ) !important;
637
+ top: calc(
638
+ var(--mw-oy, 0px) + (var(--mw-y, 0) + var(--mw-sy, 0) + var(--mw-lhs, 0)) * var(--mw-ch, 1px)
639
+ ) !important;
640
+ }
586
641
 
587
- /* Multicol TEXT LEAF (specs/multicol.md "Browser agreement"): native
588
- * columns driven by the engine's used values quantized count and gap,
589
- * sequential fill into the engine-quantized content height — so the
590
- * browser breaks on exactly the engine's lines and the native text,
591
- * caret, and selection sit under the grid's glyphs. The orphans/widows
592
- * initial value of 2 vetoes single-line column endings the engine's
593
- * model allows (probed: Chromium then breaks [3, 2, 2] where the engine
594
- * says [3, 3, 1]), so both reset to 1. */
595
- mono-wind:not([measuring]) [data-mw-multicol] {
596
- column-count: var(--mw-colc, 1) !important;
597
- column-width: auto !important;
598
- column-gap: calc(var(--mw-colg, 0) * var(--mw-cw, 1px)) !important;
599
- column-fill: auto !important;
600
- orphans: 1 !important;
601
- widows: 1 !important;
602
- /* No trailing-gap carve-out here: it would widen only the LAST column
603
- * (native columns split the content box equally), so tracked multicol
604
- * text instead wraps at `width tracking` engine-side the exact
605
- * fit rule the browser applies per column. */
606
- width: calc(var(--mw-w, 0) * var(--mw-cw, 1px) + 0.03125px) !important;
607
- padding-right: calc((var(--mw-pr, 0) + var(--mw-br, 0)) * var(--mw-cw, 1px)) !important;
608
- /* Column-axis reconstruction: the engine's box is TIGHT (each
609
- * column's last trailing leading trimmed, like single-column leaves),
610
- * but browsers count FULL line boxes when fragmenting re-extend by
611
- * the `--mw-lh 1` leading rows so every column fits its lines.
612
- * `--mw-ink` (the host-measured ink overhang WebKit breaks at ink
613
- * bottoms, and some fonts draw taller than their line box) plus one
614
- * layout unit of calc-rounding slack keep the last line's ink inside
615
- * its column. The extension is under one line box, so no engine fits
616
- * an extra line, and the extra rows are blank leading nothing
617
- * visible moves. */
618
- height: calc(
619
- (var(--mw-h, 0) + var(--mw-lh, 1) - 1) * var(--mw-ch, 1px) + var(--mw-ink, 0px) + 0.03125px
620
- ) !important;
621
- }
642
+ /* Shared cell geometry for engine-sized boxes — engine-POSITIONED
643
+ * elements, in-flow atomic inline boxes, and in-flow multicol spanners
644
+ * (below). */
645
+ mono-wind:not([measuring]) [data-mw-laid-out],
646
+ mono-wind:not([measuring]) [data-mw-inline-box],
647
+ mono-wind:not([measuring]) [data-mw-multicol-flow-span],
648
+ mono-wind:not([measuring]) [data-mw-flow],
649
+ mono-wind:not([measuring]) [data-mw-float] {
650
+ box-sizing: border-box !important;
651
+ /* A sticky shift (specs/sticky.md) belongs to the box the engine wrote
652
+ * it on: custom properties inherit, so every box resets them, plainly,
653
+ * and only the engine's inline value — which beats a plain rule — moves
654
+ * a box. */
655
+ --mw-sx: 0;
656
+ --mw-sy: 0;
657
+ /* Trailing-gap allowance: a tracked box's lines end with a phantom
658
+ * `--mw-ls`-cell gap the engine doesn't count. Give the content box that
659
+ * room by carving it out of the engine-owned right padding/border cells;
660
+ * only when those are fewer than the gap does the element box itself
661
+ * widen (invisibly the decorated border stays where the engine put it).
662
+ * The extra 1/32px is one layout unit of headroom: engines store lengths
663
+ * on a fixed grid by flooring (1/64px in Chromium and WebKit, 1/60px in
664
+ * Firefox), so `cells × cw` can land one unit BELOW the shaped advance of
665
+ * a line that fits exactly, and the browser would wrap it. Adding at
666
+ * least one unit before the floor guarantees the stored width is ≥ the
667
+ * exact product; 1/32px covers every engine's unit and is far below a
668
+ * cell (and below a device pixel), so it changes neither cell math nor
669
+ * paint. */
670
+ width: calc(
671
+ (var(--mw-w, 0) + max(0, var(--mw-ls, 0) - var(--mw-pr, 0) - var(--mw-br, 0))) *
672
+ var(--mw-cw, 1px) + 0.03125px
673
+ ) !important;
674
+ height: calc(var(--mw-h, 0) * var(--mw-ch, 1px)) !important;
675
+ min-width: 0 !important;
676
+ min-height: 0 !important;
677
+ max-width: none !important;
678
+ max-height: none !important;
679
+ border-width: 0 !important;
680
+ /* Gap rules are engine-painted glyphs (specs/gap-decorations.md);
681
+ * css-gaps browsers must not also paint their native (fractional)
682
+ * rules — e.g. between an emptied grid's explicit tracks. */
683
+ column-rule-style: none !important;
684
+ row-rule-style: none !important;
685
+ /* Authored z-index applies only where CSS says (positioned, flex/grid
686
+ * items) — absolutization would otherwise activate it everywhere. The
687
+ * engine writes --mw-z in exactly those cases; without it the calc()
688
+ * is invalid at computed-value time and z-index falls back to auto. */
689
+ z-index: calc(var(--mw-z)) !important;
690
+ /* A layer root's backdrop filter goes on its layer (specs/layers.md):
691
+ * the element's own would take in the layer's cells beneath it. */
692
+ backdrop-filter: none !important;
693
+ padding-top: calc((var(--mw-pt, 0) + var(--mw-bt, 0)) * var(--mw-ch, 1px)) !important;
694
+ padding-right: calc(
695
+ max(0, var(--mw-pr, 0) + var(--mw-br, 0) - var(--mw-ls, 0)) * var(--mw-cw, 1px)
696
+ ) !important;
697
+ padding-bottom: calc((var(--mw-pb, 0) + var(--mw-bb, 0)) * var(--mw-ch, 1px)) !important;
698
+ padding-left: calc((var(--mw-pl, 0) + var(--mw-bl, 0)) * var(--mw-cw, 1px)) !important;
699
+ margin: 0 !important;
700
+ line-height: calc(var(--mw-lh, 1) * var(--mw-ch, 1px)) !important;
701
+ }
622
702
 
623
- /* Paragraph-flow multicol children (specs/multicol.md "Fragmenting
624
- * text-leaf children"): chrome-less text leaves stay IN FLOW inside the
625
- * container's native columns so the browser fragments them itself, with
626
- * engine-quantized margins (native sibling collapsing IS the engine's
627
- * collapse rule). Width and height stay natural a chrome-less block's
628
- * natural height is full line boxes, exactly what fragmentation counts;
629
- * typography quantization and the color/background locks come from the
630
- * universal rules, and line-height inherits from the container. */
631
- mono-wind:not([measuring]) [data-mw-multicol-flow] {
632
- position: static !important;
633
- display: block !important;
634
- box-sizing: border-box !important;
635
- width: auto !important;
636
- height: auto !important;
637
- min-width: 0 !important;
638
- min-height: 0 !important;
639
- max-width: none !important;
640
- max-height: none !important;
641
- border-width: 0 !important;
642
- margin-top: calc(var(--mw-mt, 0) * var(--mw-ch, 1px)) !important;
643
- margin-right: calc(var(--mw-mr, 0) * var(--mw-cw, 1px)) !important;
644
- margin-bottom: calc(var(--mw-mb, 0) * var(--mw-ch, 1px)) !important;
645
- margin-left: calc(var(--mw-ml, 0) * var(--mw-cw, 1px)) !important;
646
- column-span: none !important;
647
- }
703
+ /* Atomic inline boxes (`inline-flex`/`inline-block`/`inline-grid` riding a
704
+ * text run, specs/cell-model.md): the box stays IN FLOW the browser's
705
+ * own line layout places it, and both models agree because the engine
706
+ * sizes it to exactly the cells its U+FFFC marker occupies in the wrap.
707
+ * `position: relative` makes it the containing block for its laid-out
708
+ * children; `vertical-align: top` pins the box's top to its line's first
709
+ * row (the global baseline lock would hang an abspos-only box below the
710
+ * line) a box taller than one row grows its line, per CSS. */
711
+ mono-wind:not([measuring]) [data-mw-inline-box] {
712
+ position: relative !important;
713
+ /* A sticky inline box's shift for the scroll (specs/sticky.md; the
714
+ * vars are absent otherwise) and the same half-leading lift as
715
+ * laid-out elements — without it an inline box's NATIVE ink (a
716
+ * textarea's value under leading-*) sits centered in its line boxes
717
+ * instead of on its rows. */
718
+ top: calc((var(--mw-sy, 0) + var(--mw-lhs, 0)) * var(--mw-ch, 1px)) !important;
719
+ left: calc(var(--mw-sx, 0) * var(--mw-cw, 1px)) !important;
720
+ vertical-align: top !important;
721
+ }
648
722
 
649
- /* text-indent (specs/cell-model.md): native px would resolve to the
650
- * root font size, not the cell width, so the transparent light-DOM
651
- * text drifts out from under the grid's paint. Rewrite it in cells so
652
- * the two agree selection ranges land exactly where the glyphs are. */
653
- mono-wind:not([measuring])[data-mw-leaf],
654
- mono-wind:not([measuring]) [data-mw-laid-out],
655
- mono-wind:not([measuring]) [data-mw-multicol-flow],
656
- mono-wind:not([measuring]) [data-mw-flow],
657
- mono-wind:not([measuring]) [data-mw-float] {
658
- text-indent: calc(var(--mw-ti, 0) * var(--mw-cw, 1px)) !important;
659
- }
723
+ /* Authored `vertical-align: bottom` on an atomic inline box: grid-exact
724
+ * in every engine (probed), so it passes through; the engine drops the
725
+ * line's text to the box's last row to match. baseline is off-grid and
726
+ * behaves as top (cell-model deviation). */
727
+ mono-wind:not([measuring]) [data-mw-inline-box][data-mw-vbottom] {
728
+ vertical-align: bottom !important;
729
+ }
730
+ /* Authored `vertical-align: middle`: a whole-row baseline length the
731
+ * engine writes (specs/cell-model.md "Typography") rows from the
732
+ * box's own baseline to its line's text row, plus the row's baseline
733
+ * for a box whose baseline is its bottom edge — so the line's text
734
+ * lands on the box's middle row in every engine (probed). */
735
+ mono-wind:not([measuring]) [data-mw-inline-box][data-mw-vmiddle] {
736
+ vertical-align: calc(
737
+ var(--mw-va, 0) * var(--mw-ch, 1px) + var(--mw-vb, 0) * var(--mw-base, 0px)
738
+ ) !important;
739
+ }
660
740
 
661
- /* Boxes that stay in the browser's flow with engine-forced geometry
662
- * (the shared rule above) and quantized engine margins: a mixed block
663
- * container's child, placed so the container's anonymous runs of text
664
- * land on their rows (specs/cell-model.md "Inline content"), and an
665
- * in-flow multicol SPANNER (specs/multicol.md), which paragraphs
666
- * fragment around. The half-leading translate doubles as the containing
667
- * block for their absolutized descendants (a transformed box contains
668
- * them like a positioned one). */
669
- mono-wind:not([measuring]) [data-mw-flow],
670
- mono-wind:not([measuring]) [data-mw-float],
671
- mono-wind:not([measuring]) [data-mw-multicol-flow-span] {
672
- position: static !important;
673
- margin-top: calc(var(--mw-mt, 0) * var(--mw-ch, 1px)) !important;
674
- margin-right: 0 !important;
675
- margin-bottom: calc(var(--mw-mb, 0) * var(--mw-ch, 1px)) !important;
676
- margin-left: calc(var(--mw-ml, 0) * var(--mw-cw, 1px)) !important;
677
- translate: 0 calc(var(--mw-lhs, 0) * var(--mw-ch, 1px)) !important;
678
- }
679
- mono-wind:not([measuring]) [data-mw-multicol-flow-span] {
680
- column-span: all !important;
681
- }
682
- /* A formatting-context root among flow children a container, or a leaf
683
- * CSS makes one keeps its own: its first child's top margin stays
684
- * inside it, where the engine put that child (the shadow slot does the
685
- * same for the host's, element.ts), and it steps aside from a float
686
- * rather than passing under (specs/float.md). */
687
- mono-wind:not([measuring]) [data-mw-flow="box"] {
688
- contain: layout !important;
689
- }
690
- /* A float (specs/float.md): the browser floats the engine's box, and
691
- * the lines of the leaf flow children beside it shorten as the engine
692
- * shortened them, while a root flow child steps aside as the engine
693
- * placed it. Both margin boxes take part in the browser's fit a
694
- * float's is the exclusion, a root's must not overlap one — so each
695
- * gives back the shared rule's widening on its right margin: the
696
- * tracking allowance and, as a quarter pixel, the layout-unit headroom.
697
- * A root or a later float that fits the engine's band exactly then
698
- * fits natively too, and a line beside a float keeps its headroom. */
699
- mono-wind:not([measuring]) [data-mw-float],
700
- mono-wind:not([measuring]) [data-mw-flow="box"] {
701
- margin-right: calc(
702
- (var(--mw-mr, 0) - max(0, var(--mw-ls, 0) - var(--mw-pr, 0) - var(--mw-br, 0))) *
703
- var(--mw-cw, 1px) - 0.25px
704
- ) !important;
705
- }
706
- mono-wind:not([measuring]) [data-mw-float="left"] {
707
- float: left !important;
708
- }
709
- mono-wind:not([measuring]) [data-mw-float="right"] {
710
- float: right !important;
711
- }
741
+ /* Multicol TEXT LEAF (specs/multicol.md "Browser agreement"): native
742
+ * columns driven by the engine's used values quantized count and gap,
743
+ * sequential fill into the engine-quantized content height so the
744
+ * browser breaks on exactly the engine's lines and the native text,
745
+ * caret, and selection sit under the grid's glyphs. The orphans/widows
746
+ * initial value of 2 vetoes single-line column endings the engine's
747
+ * model allows (probed: Chromium then breaks [3, 2, 2] where the engine
748
+ * says [3, 3, 1]), so both reset to 1. */
749
+ mono-wind:not([measuring]) [data-mw-multicol] {
750
+ column-count: var(--mw-colc, 1) !important;
751
+ column-width: auto !important;
752
+ column-gap: calc(var(--mw-colg, 0) * var(--mw-cw, 1px)) !important;
753
+ column-fill: auto !important;
754
+ orphans: 1 !important;
755
+ widows: 1 !important;
756
+ /* No trailing-gap carve-out here: it would widen only the LAST column
757
+ * (native columns split the content box equally), so tracked multicol
758
+ * text instead wraps at `width − tracking` engine-side — the exact
759
+ * fit rule the browser applies per column. */
760
+ width: calc(var(--mw-w, 0) * var(--mw-cw, 1px) + 0.03125px) !important;
761
+ padding-right: calc((var(--mw-pr, 0) + var(--mw-br, 0)) * var(--mw-cw, 1px)) !important;
762
+ /* Column-axis reconstruction: the engine's box is TIGHT (each
763
+ * column's last trailing leading trimmed, like single-column leaves),
764
+ * but browsers count FULL line boxes when fragmenting re-extend by
765
+ * the `--mw-lh 1` leading rows so every column fits its lines.
766
+ * `--mw-ink` (the host-measured ink overhang — WebKit breaks at ink
767
+ * bottoms, and some fonts draw taller than their line box) plus one
768
+ * layout unit of calc-rounding slack keep the last line's ink inside
769
+ * its column. The extension is under one line box, so no engine fits
770
+ * an extra line, and the extra rows are blank leading — nothing
771
+ * visible moves. */
772
+ height: calc(
773
+ (var(--mw-h, 0) + var(--mw-lh, 1) - 1) * var(--mw-ch, 1px) + var(--mw-ink, 0px) + 0.03125px
774
+ ) !important;
775
+ }
712
776
 
713
- /* Spanner-split paragraph flow trusts the NATIVE balancer per segment
714
- * (probed pixel-exact in all three engines under quantized inputs):
715
- * `column-fill: balance` and the natural height replace the
716
- * fill-to-computed-height reconstruction of the base multicol rule. */
717
- mono-wind:not([measuring]) [data-mw-multicol][data-mw-multicol-balance] {
718
- column-fill: balance !important;
719
- height: auto !important;
720
- }
777
+ /* Paragraph-flow multicol children (specs/multicol.md "Fragmenting
778
+ * text-leaf children"): chrome-less text leaves stay IN FLOW inside the
779
+ * container's native columns so the browser fragments them itself, with
780
+ * engine-quantized margins (native sibling collapsing IS the engine's
781
+ * collapse rule). Width and height stay natural — a chrome-less block's
782
+ * natural height is full line boxes, exactly what fragmentation counts;
783
+ * typography quantization and the color/background locks come from the
784
+ * universal rules, and line-height inherits from the container. */
785
+ mono-wind:not([measuring]) [data-mw-multicol-flow] {
786
+ position: static !important;
787
+ display: block !important;
788
+ box-sizing: border-box !important;
789
+ width: auto !important;
790
+ height: auto !important;
791
+ min-width: 0 !important;
792
+ min-height: 0 !important;
793
+ max-width: none !important;
794
+ max-height: none !important;
795
+ border-width: 0 !important;
796
+ margin-top: calc(var(--mw-mt, 0) * var(--mw-ch, 1px)) !important;
797
+ margin-right: calc(var(--mw-mr, 0) * var(--mw-cw, 1px)) !important;
798
+ margin-bottom: calc(var(--mw-mb, 0) * var(--mw-ch, 1px)) !important;
799
+ margin-left: calc(var(--mw-ml, 0) * var(--mw-cw, 1px)) !important;
800
+ column-span: none !important;
801
+ }
721
802
 
722
- /* Paragraph gaps under a spanner-split (balance) container: margins
723
- * derail the native balancers (probed), so the engine-collapsed gaps
724
- * ride as PADDING each inter-paragraph gap as the preceding
725
- * paragraph's padding-bottom (`--mw-mb`), the segment-leading gap as
726
- * the first paragraph's padding-top (`--mw-mt`). Chromium and Firefox
727
- * keep a trailing padding monolithic with its last line at a column
728
- * break (probed), so a gap sits invisibly at a column bottom and the
729
- * next paragraph starts flush at the column top — the CSS-truncation
730
- * look. WebKit slice-spills padding at breaks instead, and its gate
731
- * (multicol.ts detectGluedPreBreak) keeps margined spanner containers
732
- * on atomic distribution there. */
733
- mono-wind:not([measuring]) [data-mw-multicol-balance] > [data-mw-multicol-flow] {
734
- margin-top: 0 !important;
735
- margin-bottom: 0 !important;
736
- padding-top: calc(var(--mw-mt, 0) * var(--mw-ch, 1px)) !important;
737
- padding-bottom: calc(var(--mw-mb, 0) * var(--mw-ch, 1px)) !important;
738
- }
803
+ /* text-indent (specs/cell-model.md): native px would resolve to the
804
+ * root font size, not the cell width, so the transparent light-DOM
805
+ * text drifts out from under the grid's paint. Rewrite it in cells so
806
+ * the two agree — selection ranges land exactly where the glyphs are. */
807
+ mono-wind:not([measuring])[data-mw-leaf],
808
+ mono-wind:not([measuring]) [data-mw-laid-out],
809
+ mono-wind:not([measuring]) [data-mw-multicol-flow],
810
+ mono-wind:not([measuring]) [data-mw-flow],
811
+ mono-wind:not([measuring]) [data-mw-float] {
812
+ text-indent: calc(var(--mw-ti, 0) * var(--mw-cw, 1px)) !important;
813
+ }
739
814
 
740
- /* Author-requested `overflow: hidden` or `overflow: clip` both keep
741
- * content inside the engine-allocated box. Uses `clip` (no scroll
742
- * container, cheaper, more precise semantic for what we're doing). */
743
- mono-wind [data-mw-clip] {
744
- overflow: clip !important;
745
- }
815
+ /* Boxes that stay in the browser's flow with engine-forced geometry
816
+ * (the shared rule above) and quantized engine margins: a mixed block
817
+ * container's child, placed so the container's anonymous runs of text
818
+ * land on their rows (specs/cell-model.md "Inline content"), and an
819
+ * in-flow multicol SPANNER (specs/multicol.md), which paragraphs
820
+ * fragment around. `relative` makes them the containing block for
821
+ * their absolutized descendants and carries the half-leading lift;
822
+ * the engine owns every inset, so an authored one stays inert. */
823
+ mono-wind:not([measuring]) [data-mw-flow],
824
+ mono-wind:not([measuring]) [data-mw-float],
825
+ mono-wind:not([measuring]) [data-mw-multicol-flow-span] {
826
+ position: relative !important;
827
+ top: calc(var(--mw-lhs, 0) * var(--mw-ch, 1px)) !important;
828
+ right: auto !important;
829
+ bottom: auto !important;
830
+ left: auto !important;
831
+ margin-top: calc(var(--mw-mt, 0) * var(--mw-ch, 1px)) !important;
832
+ margin-right: 0 !important;
833
+ margin-bottom: calc(var(--mw-mb, 0) * var(--mw-ch, 1px)) !important;
834
+ margin-left: calc(var(--mw-ml, 0) * var(--mw-cw, 1px)) !important;
835
+ }
836
+ mono-wind:not([measuring]) [data-mw-multicol-flow-span] {
837
+ column-span: all !important;
838
+ }
839
+ /* A formatting-context root among flow children — a container, or a leaf
840
+ * CSS makes one — keeps its own: its first child's top margin stays
841
+ * inside it, where the engine put that child (the shadow slot does the
842
+ * same for the host's, element.ts), and it steps aside from a float
843
+ * rather than passing under (specs/float.md). */
844
+ mono-wind:not([measuring]) [data-mw-flow="box"] {
845
+ contain: layout !important;
846
+ }
847
+ /* A float (specs/float.md): the browser floats the engine's box, and
848
+ * the lines of the leaf flow children beside it shorten as the engine
849
+ * shortened them, while a root flow child steps aside as the engine
850
+ * placed it. Both margin boxes take part in the browser's fit — a
851
+ * float's is the exclusion, a root's must not overlap one — so each
852
+ * gives back the shared rule's widening on its right margin: the
853
+ * tracking allowance and, as a quarter pixel, the layout-unit headroom.
854
+ * A root or a later float that fits the engine's band exactly then
855
+ * fits natively too, and a line beside a float keeps its headroom. */
856
+ mono-wind:not([measuring]) [data-mw-float],
857
+ mono-wind:not([measuring]) [data-mw-flow="box"] {
858
+ margin-right: calc(
859
+ (var(--mw-mr, 0) - max(0, var(--mw-ls, 0) - var(--mw-pr, 0) - var(--mw-br, 0))) *
860
+ var(--mw-cw, 1px) - 0.25px
861
+ ) !important;
862
+ }
863
+ mono-wind:not([measuring]) [data-mw-float="left"] {
864
+ float: left !important;
865
+ }
866
+ mono-wind:not([measuring]) [data-mw-float="right"] {
867
+ float: right !important;
868
+ }
746
869
 
747
- /* Inline (relative/sticky) elements with authored relative insets
748
- * (specs/positioning.md): the engine writes the cell-rounded offsets into
749
- * `--mw-it/ir/ib/il` and this rule applies them on the grid. A side
750
- * without its var is invalid at computed-value time and falls back to
751
- * `auto` — so authored subsets work and the author's raw off-grid values
752
- * never apply. Gated on `measuring` so the style reader sees the authored
753
- * values (no echo loop). Also pins `position: relative` — sticky behaves
754
- * as relative until the scrolling milestone. */
755
- mono-wind:not([measuring]) [data-mw-inline-inset] {
756
- position: relative !important;
757
- top: calc(var(--mw-it) * var(--mw-ch, 1px)) !important;
758
- right: calc(var(--mw-ir) * var(--mw-cw, 1px)) !important;
759
- bottom: calc(var(--mw-ib) * var(--mw-ch, 1px)) !important;
760
- left: calc(var(--mw-il) * var(--mw-cw, 1px)) !important;
761
- }
870
+ /* Spanner-split paragraph flow trusts the NATIVE balancer per segment
871
+ * (probed pixel-exact in all three engines under quantized inputs):
872
+ * `column-fill: balance` and the natural height replace the
873
+ * fill-to-computed-height reconstruction of the base multicol rule. */
874
+ mono-wind:not([measuring]) [data-mw-multicol][data-mw-multicol-balance] {
875
+ column-fill: balance !important;
876
+ height: auto !important;
877
+ }
762
878
 
763
- /* === Engine state attributes (warnings, hiding, alignment) =========== */
879
+ /* Paragraph gaps under a spanner-split (balance) container: margins
880
+ * derail the native balancers (probed), so the engine-collapsed gaps
881
+ * ride as PADDING — each inter-paragraph gap as the preceding
882
+ * paragraph's padding-bottom (`--mw-mb`), the segment-leading gap as
883
+ * the first paragraph's padding-top (`--mw-mt`). Chromium and Firefox
884
+ * keep a trailing padding monolithic with its last line at a column
885
+ * break (probed), so a gap sits invisibly at a column bottom and the
886
+ * next paragraph starts flush at the column top — the CSS-truncation
887
+ * look. WebKit slice-spills padding at breaks instead, and its gate
888
+ * (multicol.ts detectGluedPreBreak) keeps margined spanner containers
889
+ * on atomic distribution there. */
890
+ mono-wind:not([measuring]) [data-mw-multicol-balance] > [data-mw-multicol-flow] {
891
+ margin-top: 0 !important;
892
+ margin-bottom: 0 !important;
893
+ padding-top: calc(var(--mw-mt, 0) * var(--mw-ch, 1px)) !important;
894
+ padding-bottom: calc(var(--mw-mb, 0) * var(--mw-ch, 1px)) !important;
895
+ }
764
896
 
765
- /* Misparented table content (no anonymous boxes, specs/table.md) and
766
- * <col>/<colgroup> boxes: fully hidden, subtree included. */
767
- mono-wind:not([measuring]) [data-mw-table-hidden] {
768
- display: none !important;
769
- }
897
+ /* Author-requested `overflow: hidden` or `overflow: clip` both keep
898
+ * content inside the engine-allocated box. Uses `clip` (no scroll
899
+ * container, cheaper, more precise semantic for what we're doing). */
900
+ mono-wind [data-mw-clip] {
901
+ overflow: clip !important;
902
+ }
770
903
 
771
- /* A container whose anonymous runs are misparented table content
772
- * (stray text in a div-table, specs/table.md): the runs have no element
773
- * to hide, so the container hides its text, and its laid-out children
774
- * the rows or cells show through the inherited value. */
775
- mono-wind:not([measuring]) [data-mw-hidden-runs] {
776
- visibility: hidden !important;
777
- }
778
- mono-wind:not([measuring]) [data-mw-hidden-runs] > [data-mw-laid-out] {
779
- visibility: visible !important;
780
- }
904
+ /* Inline (relative/sticky) elements with authored relative insets
905
+ * (specs/positioning.md): the engine writes the cell-rounded offsets into
906
+ * `--mw-it/ir/ib/il` and this rule applies them on the grid. A side
907
+ * without its var is invalid at computed-value time and falls back to
908
+ * `auto` — so authored subsets work and the author's raw off-grid values
909
+ * never apply. Gated on `measuring` so the style reader sees the authored
910
+ * values (no echo loop). Also pins `position: relative` — sticky behaves
911
+ * as relative until the scrolling milestone. */
912
+ mono-wind:not([measuring]) [data-mw-inline-inset] {
913
+ position: relative !important;
914
+ top: calc(var(--mw-it) * var(--mw-ch, 1px)) !important;
915
+ right: calc(var(--mw-ir) * var(--mw-cw, 1px)) !important;
916
+ bottom: calc(var(--mw-ib) * var(--mw-ch, 1px)) !important;
917
+ left: calc(var(--mw-il) * var(--mw-cw, 1px)) !important;
918
+ }
919
+
920
+ /* === Engine state attributes (warnings, hiding, alignment) =========== */
781
921
 
782
- /* Force off-grid text alignments back to `start`. `text-left`/`right`/`start`/
783
- * `end`/`center` all land on whole cells (offset = (W - N) × cell width);
784
- * `justify` produces fractional per-line offsets we can't snap to the grid. */
785
- mono-wind:not([measuring])[data-mw-text-align-blocked],
786
- mono-wind:not([measuring]) [data-mw-text-align-blocked] {
787
- text-align: start !important;
922
+ /* Misparented table content (no anonymous boxes, specs/table.md) and
923
+ * <col>/<colgroup> boxes: fully hidden, subtree included. */
924
+ mono-wind:not([measuring]) [data-mw-table-hidden] {
925
+ display: none !important;
926
+ }
927
+
928
+ /* A container whose anonymous runs are misparented table content
929
+ * (stray text in a div-table, specs/table.md): the runs have no element
930
+ * to hide, so the container hides its text, and its laid-out children
931
+ * — the rows or cells — show through the inherited value. */
932
+ mono-wind:not([measuring]) [data-mw-hidden-runs] {
933
+ visibility: hidden !important;
934
+ }
935
+ mono-wind:not([measuring]) [data-mw-hidden-runs] > [data-mw-laid-out] {
936
+ visibility: visible !important;
937
+ }
938
+
939
+ /* Force off-grid text alignments back to `start`. `text-left`/`right`/`start`/
940
+ * `end`/`center` all land on whole cells (offset = (W - N) × cell width);
941
+ * `justify` produces fractional per-line offsets we can't snap to the grid. */
942
+ mono-wind:not([measuring])[data-mw-text-align-blocked],
943
+ mono-wind:not([measuring]) [data-mw-text-align-blocked] {
944
+ text-align: start !important;
945
+ }
788
946
  }