monowind 0.2.14 → 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,493 +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,
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
- }
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
+ }
338
436
 
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
- }
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
+ }
349
445
 
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
- }
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
+ }
394
455
 
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
- }
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
+ }
413
466
 
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
- }
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
+ }
423
511
 
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
- }
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
+ }
431
519
 
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
- }
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
+ }
443
538
 
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
- }
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
+ }
451
548
 
452
- /* === 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
+ }
453
556
 
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
- }
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
+ }
501
568
 
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
- }
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
+ }
562
576
 
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
- }
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
+ }
582
628
 
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
- }
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
+ }
590
641
 
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
- }
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
+ }
626
702
 
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
- }
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
+ }
652
722
 
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
- }
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
+ }
664
740
 
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
- }
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
+ }
719
776
 
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
- }
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
+ }
728
802
 
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
- }
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
+ }
746
814
 
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
- }
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
+ }
753
869
 
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
- }
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
+ }
769
878
 
770
- /* === 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
+ }
771
896
 
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
- }
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
+ }
777
903
 
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
- }
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) =========== */
788
921
 
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;
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
+ }
795
946
  }