monowind 0.2.14 → 0.3.1

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