@axiapps/axi-design 1.33.0 → 1.35.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/dist/axi.css CHANGED
@@ -84,7 +84,26 @@
84
84
  against a fill lighter than itself. Every outline in this language is
85
85
  near-black on the assumption of a ground the lines can be darker than; a
86
86
  well IS at ground level, so an ink edge round one is a black line on a
87
- black field. Raised takes ink, sunk takes the rule. */
87
+ black field. Raised takes ink, sunk takes the rule.
88
+
89
+ And what the fill names is not the .axi-well class - it is the level. A
90
+ button at rest, a field, a select, a checkbox, an avatar, a rank, a code
91
+ span, a hovered row in a popover: not one of them is raised off what holds
92
+ it, and every one of them was spelled --axi-ground, because on a theme
93
+ whose surfaces are opaque "flat on its container" and "the page colour"
94
+ are the same colour. They stop being the same colour the instant a theme
95
+ makes the container translucent, which is how the whole set went black at
96
+ once and why this is written here rather than fourteen times downstream.
97
+ Above this level is --axi-surface (.axi-kbd is the one control in the
98
+ language that is genuinely raised, and says so). Below it is --axi-ground
99
+ proper, which now means the page and only the page: body, the sheet, the
100
+ window, the masthead - the things content scrolls under, which need real
101
+ opacity and not a subtraction. Everything between the two is this token.
102
+
103
+ The LINE does not come along. --axi-well-line is for a well cut into the
104
+ page, where there is nothing left for ink to be darker than; a control is
105
+ cut into a SURFACE and its ink edge still has a fill above it to read
106
+ against. Every component below keeps --axi-ink-line. */
88
107
  --axi-well-fill: var(--axi-ground);
89
108
  --axi-well-line: var(--axi-rule);
90
109
  --axi-text: #f4f6f9;
@@ -371,13 +390,29 @@ button.axi-panel--tile:hover,
371
390
  padding: var(--axi-well-pad, 10px);
372
391
  }
373
392
 
393
+ /* The reading-scale well, which the comment above describes and this language
394
+ then made every consumer spell out by hand. Saying `--axi-radius-sm` inline
395
+ is fine for the handful of instances that comment imagined; the first real
396
+ consumer to be counted had fifty, and fifty inline style attributes is the
397
+ thing a class exists to prevent. The same argument produced .axi-btn--sm.
398
+
399
+ Only the radius, because that is the only thing that changes: a recess the
400
+ size of a single reading still holds objects that carry their own padding, so
401
+ the pad knob is still the right way to say anything else. */
402
+ .axi-well--sm { --axi-well-radius: var(--axi-radius-sm); }
403
+
374
404
  /* ---------- button ---------- */
375
405
  .axi-btn {
376
406
  display: inline-flex; align-items: center; gap: 8px;
377
407
  padding: var(--axi-btn-pad, 12px 20px);
378
408
  border: var(--axi-border-control) solid var(--axi-ink-line);
379
409
  border-radius: var(--axi-radius-sm);
380
- background: var(--axi-ground);
410
+ /* Flat on its container, which is --axi-well-fill and not the page colour -
411
+ the argument is in src/tokens.css at that token. A button at rest carries
412
+ no block: it is level with the panel holding it and lifts on hover, so the
413
+ resting fill is the level's, and --axi-btn--primary below is the one that
414
+ rests raised and fills with the accent instead. */
415
+ background: var(--axi-well-fill);
381
416
  color: var(--axi-text-dim);
382
417
  font: var(--axi-t-label);
383
418
  letter-spacing: var(--axi-ls-label);
@@ -460,7 +495,8 @@ button.axi-panel--tile:hover,
460
495
  padding: var(--axi-pill-pad, 12px 20px);
461
496
  border: var(--axi-border-control) solid var(--axi-ink-line);
462
497
  border-radius: var(--axi-radius-sm);
463
- background: var(--axi-ground);
498
+ /* The button's fill for the button's reason - see .axi-btn above. */
499
+ background: var(--axi-well-fill);
464
500
  color: var(--axi-text-dim);
465
501
  font: var(--axi-t-label);
466
502
  letter-spacing: var(--axi-ls-label);
@@ -654,7 +690,10 @@ button.axi-panel--tile:hover,
654
690
  .axi-input {
655
691
  width: 100%;
656
692
  padding: var(--axi-input-pad, 11px 12px);
657
- background: var(--axi-ground);
693
+ /* A field is the recessed thing in any bar it sits in - .axi-kbd's comment
694
+ below leans on exactly that - so it takes the level's fill. See
695
+ --axi-well-fill in src/tokens.css. */
696
+ background: var(--axi-well-fill);
658
697
  border: var(--axi-border-control) solid var(--axi-ink-line);
659
698
  border-radius: var(--axi-radius-sm);
660
699
  color: var(--axi-text);
@@ -689,14 +728,15 @@ button.axi-panel--tile:hover,
689
728
  `em`, not `px`: this lands in body copy and in 10px captions alike, and a
690
729
  literal that does not track the text around it reads as a different voice.
691
730
 
692
- It sits on --axi-ground where .axi-kbd sits on --axi-surface, and the
731
+ It sits on --axi-well-fill where .axi-kbd sits on --axi-surface, and the
693
732
  reason is already written down one rule below: a key is raised off what it
694
- is printed on, and a quoted literal is sunk into it. */
733
+ is printed on, and a quoted literal is sunk into it. It said --axi-ground
734
+ for as long as sunk and the page were the same colour; see that token. */
695
735
  .axi-code,
696
736
  .axi-prose code {
697
737
  font-family: var(--axi-mono);
698
738
  font-size: .88em;
699
- background: var(--axi-ground);
739
+ background: var(--axi-well-fill);
700
740
  color: var(--axi-text);
701
741
  border: var(--axi-border-hairline) solid var(--axi-ink-line);
702
742
  border-radius: var(--axi-radius-sm);
@@ -736,7 +776,10 @@ button.axi-panel--tile:hover,
736
776
  padding: 10px 30px 10px 9px;
737
777
  border: var(--axi-border-control) solid var(--axi-ink-line);
738
778
  border-radius: var(--axi-radius-sm);
739
- background-color: var(--axi-ground);
779
+ /* The field's fill, because a closed select is a field - see .axi-input. The
780
+ longhand stays: a caret rides in background-image and the shorthand would
781
+ reset it. */
782
+ background-color: var(--axi-well-fill);
740
783
  color: var(--axi-text-dim);
741
784
  font: var(--axi-t-label);
742
785
  font-size: 12.5px;
@@ -795,8 +838,12 @@ button.axi-panel--tile:hover,
795
838
  letter-spacing: var(--axi-ls-label);
796
839
  text-transform: uppercase;
797
840
  }
841
+ /* A row under the cursor sinks rather than lighting up, so the hover is the
842
+ level's fill - and this popover is filled with --axi-surface-raised, which
843
+ a theme may take translucent, so an opaque row here is the black patch
844
+ --axi-well-fill exists to prevent. Same for the picker's rows below. */
798
845
  .axi-select option:hover, .axi-select option:focus {
799
- background: var(--axi-ground); color: var(--axi-text);
846
+ background: var(--axi-well-fill); color: var(--axi-text);
800
847
  }
801
848
  /* The page-wide focus ring sits 2px outside its element; inside a picker
802
849
  that is 2px into the neighbouring row, so pull it back in. */
@@ -895,7 +942,7 @@ button.axi-panel--tile:hover,
895
942
  font-weight: 900;
896
943
  }
897
944
  .axi-picker__opt:hover, .axi-picker__opt:focus {
898
- background: var(--axi-ground); color: var(--axi-text);
945
+ background: var(--axi-well-fill); color: var(--axi-text);
899
946
  }
900
947
  /* The page-wide focus ring sits 2px outside its element; inside a popover
901
948
  this tight that is 2px into the neighbouring row, so pull it back in. */
@@ -910,7 +957,8 @@ button.axi-panel--tile:hover,
910
957
  who wants round avatars sets --axi-radius-sm and gets rounded controls
911
958
  everywhere - which is the honest version of the request.
912
959
  Flat: outlined, no block. An avatar is content inside a panel, the same as
913
- .axi-stat and .axi-table__rank, not a thing raised off it. */
960
+ .axi-stat and .axi-table__rank, not a thing raised off it - which is what
961
+ --axi-well-fill below is saying, and all three now say it the same way. */
914
962
  .axi-avatar {
915
963
  width: var(--axi-avatar-size, 40px);
916
964
  height: var(--axi-avatar-size, 40px);
@@ -918,7 +966,7 @@ button.axi-panel--tile:hover,
918
966
  display: grid;
919
967
  place-items: center;
920
968
  overflow: hidden;
921
- background: var(--axi-ground);
969
+ background: var(--axi-well-fill);
922
970
  color: var(--axi-text-dim);
923
971
  border: var(--axi-border-control) solid var(--axi-ink-line);
924
972
  border-radius: var(--axi-radius-sm);
@@ -983,7 +1031,9 @@ button.axi-panel--tile:hover,
983
1031
  height: var(--axi-check-size, 22px);
984
1032
  display: inline-grid;
985
1033
  place-items: center;
986
- background: var(--axi-ground);
1034
+ /* An empty box is a hole you put a mark in, so the level's fill - see
1035
+ --axi-well-fill in src/tokens.css. :checked below replaces it outright. */
1036
+ background: var(--axi-well-fill);
987
1037
  border: var(--axi-border-control) solid var(--axi-ink-line);
988
1038
  /* The text-sized step, not the control-sized one - see --axi-radius-xs in
989
1039
  src/tokens.css for why a 22px box cannot take a corner scaled for a 36px
@@ -1105,9 +1155,18 @@ textarea.axi-input {
1105
1155
  }
1106
1156
 
1107
1157
  /* ---------- masthead ---------- */
1158
+ /* Opaque, because content scrolls under it. Which is also why it paints the
1159
+ page's light as well as the page's colour: an opaque strip across the top of
1160
+ a lit page is a dark band over that light, and the band is there on every
1161
+ page of a site the whole time it is open. Attachment fixed, the same as
1162
+ `body`, so the strip's share of the light is the share the page would have
1163
+ shown there - a wash positioned to this element's own box instead would
1164
+ line up with nothing. */
1108
1165
  .axi-mast {
1109
1166
  position: sticky; top: 0; z-index: 40;
1110
- background: var(--axi-ground);
1167
+ background-color: var(--axi-ground);
1168
+ background-image: var(--axi-ground-image);
1169
+ background-attachment: fixed;
1111
1170
  border-bottom: var(--axi-border-panel) solid var(--axi-ink-line);
1112
1171
  }
1113
1172
  .axi-mast__in {
@@ -1318,6 +1377,41 @@ textarea.axi-input {
1318
1377
  the faint tone, and inside a filled item it follows the item's ink. */
1319
1378
  .axi-rail__item .axi-icon { color: var(--axi-text-faint); }
1320
1379
  .axi-rail__item[aria-current] .axi-icon { color: var(--axi-accent-ink); }
1380
+ /* A rail nested inside a panel whose content already spends the accent: a
1381
+ metric picker beside the table it drives, a filter list beside its results.
1382
+ The language already refuses two fills at two levels inside one rail - that
1383
+ is why .axi-rail__subitem has no fill - and a rail that is itself the inner
1384
+ level is the same refusal one container further out. Two filled rails on
1385
+ one screen name two places, and only one of them is where you are.
1386
+
1387
+ What it cannot do is borrow the subitem's answer. A subitem is one of a
1388
+ handful of leaves under an open category; a picker like this is twenty rows
1389
+ and the primary control of its own panel, so brightened text alone loses
1390
+ the selection in the list. This is the third weight between the two: the
1391
+ row rises off the rail as any hovered row does, and the accent arrives on
1392
+ its leading edge. The accent still says which row without the row claiming
1393
+ to be the place.
1394
+
1395
+ The edge is the item's own border, which .axi-rail__item already reserves
1396
+ at the control weight and draws transparent - so the bar costs no shadow,
1397
+ no extra box and no reflow when it lights up. Logical, not left: this is
1398
+ the leading edge, the same correction .axi-rail--flush took.
1399
+
1400
+ The modifier goes on the list, not on .axi-rail, because a nested picker
1401
+ usually has no rail box around it - it sits directly in the panel or well
1402
+ that holds it, and inheriting a 208px width and a panel shadow is the
1403
+ opposite of what it wants. */
1404
+ .axi-rail__nav--quiet .axi-rail__item[aria-current],
1405
+ .axi-rail__nav--quiet .axi-rail__item[aria-current]:hover {
1406
+ background: var(--axi-surface-raised);
1407
+ color: var(--axi-text);
1408
+ border-color: transparent;
1409
+ border-inline-start-color: var(--axi-accent);
1410
+ }
1411
+ /* No accent fill here, so the icon has no accent ink to follow - it follows
1412
+ the row's own text, the way it does in an unselected item. */
1413
+ .axi-rail__nav--quiet .axi-rail__item[aria-current] .axi-icon { color: var(--axi-text); }
1414
+
1321
1415
  /* The second level, indented under the item it belongs to. */
1322
1416
  .axi-rail__sub { display: flex; flex-direction: column; margin: 3px 0 5px 10px; }
1323
1417
  .axi-rail__subitem {
@@ -1483,7 +1577,8 @@ textarea.axi-input {
1483
1577
  padding: 7px 8px; border-radius: var(--axi-radius-sm);
1484
1578
  font-size: 13px; font-weight: 600; color: var(--axi-text-dim); cursor: pointer;
1485
1579
  }
1486
- .axi-menu__pop label:hover { background: var(--axi-ground); color: var(--axi-text); }
1580
+ /* A hovered row sinks - the same fill the select's and the picker's rows use. */
1581
+ .axi-menu__pop label:hover { background: var(--axi-well-fill); color: var(--axi-text); }
1487
1582
  .axi-menu__pop input { margin: 3px 0 0; }
1488
1583
 
1489
1584
  /* ---------- command palette ---------- */
@@ -1597,7 +1692,7 @@ textarea.axi-input {
1597
1692
  .axi-palette__trigger {
1598
1693
  display: flex; align-items: center; width: 100%; overflow: hidden;
1599
1694
  padding: 0 10px 0 0;
1600
- background: var(--axi-ground);
1695
+ background: var(--axi-well-fill);
1601
1696
  border: var(--axi-border-control) solid var(--axi-ink-line);
1602
1697
  border-radius: var(--axi-radius-sm);
1603
1698
  color: var(--axi-text-dim);
@@ -1781,7 +1876,8 @@ textarea.axi-input {
1781
1876
  width: 32px; height: 32px;
1782
1877
  display: grid; place-items: center;
1783
1878
  border-radius: var(--axi-radius-sm);
1784
- background: var(--axi-ground);
1879
+ /* A control flat on the drawer's head - see --axi-well-fill in tokens. */
1880
+ background: var(--axi-well-fill);
1785
1881
  border: var(--axi-border-control) solid var(--axi-ink-line);
1786
1882
  color: var(--axi-text-dim);
1787
1883
  font-size: 15px; font-weight: 900; cursor: pointer;
@@ -1847,6 +1943,13 @@ textarea.axi-input {
1847
1943
  overflow-y: auto;
1848
1944
  background-color: var(--axi-ground);
1849
1945
  background-image: var(--axi-ground-image);
1946
+ /* Fixed for both of the reasons `body` is: the light stays put while the
1947
+ sheet's own content scrolls, and it is positioned to the viewport rather
1948
+ than to this box - which matters here because --axi-sheet-top pushes that
1949
+ box down under an app's titlebar, and a wash measured from its top edge
1950
+ would sit lower than the one on the page it covers. Opening a sheet would
1951
+ nudge the light sideways. */
1952
+ background-attachment: fixed;
1850
1953
  padding: var(--axi-sheet-pad, 12px 16px);
1851
1954
  }
1852
1955
  /* The heading the sheet opened with, divided from the body by a rule. Rule 8:
@@ -1869,7 +1972,8 @@ textarea.axi-input {
1869
1972
  margin: 0;
1870
1973
  padding: 11px 13px;
1871
1974
  border-left: var(--axi-border-panel) solid var(--axi-accent);
1872
- background: var(--axi-ground);
1975
+ /* Sunk into the prose around it, so the well's fill - see .axi-stat. */
1976
+ background: var(--axi-well-fill);
1873
1977
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
1874
1978
  }
1875
1979
  .axi-quote :where(p) { margin: 0; font-size: 13px; font-style: italic; color: var(--axi-text-dim); line-height: 1.5; }
@@ -1886,13 +1990,24 @@ textarea.axi-input {
1886
1990
  takes the panel outline. It carries no block, because a block is an
1887
1991
  element's shadow on the surface behind it and there is nothing behind a
1888
1992
  window that this language is entitled to draw on.
1993
+
1994
+ It is the page. Not a page inside one - the only one, for as long as the app
1995
+ is running, which is why it paints the ground's IMAGE as well as the
1996
+ ground's colour. `body` is behind it and lights nothing, because a window
1997
+ covers the viewport and is opaque by the same requirement that makes it
1998
+ opaque over its own content. A window that skipped the image would render a
1999
+ theme whose character is the light - glass - as flat near-black: every panel
2000
+ inside it correctly translucent, over nothing. Longhands for the reason
2001
+ .axi-sheet gives at length below; no attachment, because this element's box
2002
+ IS the viewport and never scrolls, so fixed and scroll paint the same pixels.
1889
2003
  The titlebar is filled with the ink line rather than a surface: it is the
1890
2004
  outline widened into a strip, which is why the app's content reads as
1891
2005
  sitting inside the outline instead of under a second toolbar. */
1892
2006
  .axi-window {
1893
2007
  height: 100vh;
1894
2008
  display: flex; flex-direction: column; overflow: hidden;
1895
- background: var(--axi-ground);
2009
+ background-color: var(--axi-ground);
2010
+ background-image: var(--axi-ground-image);
1896
2011
  border: var(--axi-border-panel) solid var(--axi-ink-line);
1897
2012
  border-radius: var(--axi-radius);
1898
2013
  }
@@ -2159,10 +2274,18 @@ textarea.axi-input {
2159
2274
  an outline and no block - it is content, not something raised off the
2160
2275
  surface it sits on. The number stays in --axi-text unless it has a real
2161
2276
  state: rule 5 applies to a figure exactly as it applies to a chip, and a
2162
- tile coloured for emphasis is decoration impersonating status. */
2277
+ tile coloured for emphasis is decoration impersonating status.
2278
+
2279
+ Flat on the ground is the WELL's fill and not the page's. A tile is a recess
2280
+ cut into the panel holding it, which is the same shape .axi-well names, and
2281
+ spelled --axi-ground it stops being a recess the moment a theme makes that
2282
+ panel translucent: an opaque page colour inside a pane of glass is a black
2283
+ patch. The outline stays the ink line rather than following the well's,
2284
+ because a tile is cut into a SURFACE and an ink edge still has something to
2285
+ be darker than there. Same split in .axi-meter and .axi-plot below. */
2163
2286
  .axi-stat {
2164
2287
  padding: 12px 14px;
2165
- background: var(--axi-ground);
2288
+ background: var(--axi-well-fill);
2166
2289
  border: var(--axi-border-control) solid var(--axi-ink-line);
2167
2290
  border-radius: var(--axi-radius-sm);
2168
2291
  }
@@ -2249,7 +2372,8 @@ textarea.axi-input {
2249
2372
  display: grid; place-items: center;
2250
2373
  border: var(--axi-border-control) solid var(--axi-ink-line);
2251
2374
  border-radius: var(--axi-radius-sm);
2252
- background: var(--axi-ground);
2375
+ /* Flat in the row, like the tile above and the avatar it stands beside. */
2376
+ background: var(--axi-well-fill);
2253
2377
  font: var(--axi-t-micro);
2254
2378
  color: var(--axi-text-faint);
2255
2379
  }
@@ -2353,7 +2477,8 @@ textarea.axi-input {
2353
2477
  .axi-meter {
2354
2478
  display: flex;
2355
2479
  height: var(--axi-meter-h, 12px);
2356
- background: var(--axi-ground);
2480
+ /* The trough is a recess, so it takes the well's fill - see .axi-stat. */
2481
+ background: var(--axi-well-fill);
2357
2482
  border: var(--axi-border-control) solid var(--axi-ink-line);
2358
2483
  border-radius: var(--axi-radius-sm);
2359
2484
  overflow: hidden;
@@ -2469,7 +2594,8 @@ textarea.axi-input {
2469
2594
  .axi-plot {
2470
2595
  position: relative;
2471
2596
  height: var(--axi-plot-h, 180px);
2472
- background-color: var(--axi-ground);
2597
+ /* A frame cut into the panel, not laid on the page - see .axi-stat. */
2598
+ background-color: var(--axi-well-fill);
2473
2599
  background-image: repeating-linear-gradient(
2474
2600
  to top,
2475
2601
  var(--axi-rule) 0 var(--axi-border-hairline),
@@ -2639,9 +2765,12 @@ textarea.axi-input {
2639
2765
 
2640
2766
  /* .axi-prose code is declared beside .axi-code in primitives.css: one rule,
2641
2767
  both selectors, because they are one object. */
2768
+ /* A code block and a quote are both fields sunk into the prose around them,
2769
+ so both take --axi-well-fill rather than the page colour - the reason is
2770
+ written out at .axi-stat in src/data.css. */
2642
2771
  .axi-prose pre {
2643
2772
  margin: 0 0 18px; padding: 14px 16px; overflow-x: auto;
2644
- background: var(--axi-ground);
2773
+ background: var(--axi-well-fill);
2645
2774
  border: var(--axi-border-control) solid var(--axi-ink-line);
2646
2775
  border-radius: var(--axi-radius-sm);
2647
2776
  box-shadow: var(--axi-shadow-control);
@@ -2652,7 +2781,7 @@ textarea.axi-input {
2652
2781
  margin: 0 0 18px;
2653
2782
  padding: 11px 13px;
2654
2783
  border-left: var(--axi-border-panel) solid var(--axi-accent);
2655
- background: var(--axi-ground);
2784
+ background: var(--axi-well-fill);
2656
2785
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
2657
2786
  }
2658
2787
  .axi-prose blockquote p { margin: 0; font-style: italic; }
package/docs/RULES.md CHANGED
@@ -702,3 +702,34 @@ whole section exists to prevent.
702
702
 
703
703
  The test to write is not "does `.axi-code` exist". It is "are both spellings in
704
704
  the same rule", because only the second one fails when someone splits them.
705
+
706
+ ### A refusal holds at every level, not just the one it was written for
707
+
708
+ The rail refuses two accent fills inside itself. That is why
709
+ `.axi-rail__subitem` is brightened text and not a second filled row: with two
710
+ fills the reader has to decide which of them answers "where am I", and the
711
+ answer to one question cannot be two things.
712
+
713
+ Written that way, the refusal sounds like it is about indentation. It is not.
714
+ It is about how many times one screen may claim to be a place, and nesting a
715
+ whole rail inside a panel is the same arithmetic as nesting a row inside a
716
+ rail. A stats page with a category rail down the side and a twenty-row metric
717
+ picker inside each section has two rails, both correct on their own, and both
718
+ filled — so the screen makes the claim twice and neither wins. The app that
719
+ hit this had already reasoned its way to the same conclusion and written the
720
+ quiet treatment by hand, in its own stylesheet, with a comment giving exactly
721
+ this reason. Two parties deriving the same rule independently is the signal
722
+ that the rule belongs in the language, not in either party's override file.
723
+
724
+ So when adding a component, take every refusal the neighbouring components
725
+ state and ask what it is really counting. If the answer is "per screen" rather
726
+ than "per box", the component needs a way to stand down — and the modifier is
727
+ cheaper than the override every consumer writes instead. `.axi-rail__nav--quiet`
728
+ is that: the fill goes, the accent stays as the leading edge, and nothing else
729
+ moves.
730
+
731
+ What the modifier must not do is reach for the weaker treatment that already
732
+ exists. A subitem drops the fill *and* the weight, which is right for a few
733
+ leaves under an open category and wrong for a picker that is the primary
734
+ control of its own panel — there, brightened text loses the selection in the
735
+ list. Standing down is one step, not two.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiapps/axi-design",
3
- "version": "1.33.0",
3
+ "version": "1.35.0",
4
4
  "description": "The design language for the axi suite — flat and outlined, dark, drawn in saturated ink.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/data.css CHANGED
@@ -15,10 +15,18 @@
15
15
  an outline and no block - it is content, not something raised off the
16
16
  surface it sits on. The number stays in --axi-text unless it has a real
17
17
  state: rule 5 applies to a figure exactly as it applies to a chip, and a
18
- tile coloured for emphasis is decoration impersonating status. */
18
+ tile coloured for emphasis is decoration impersonating status.
19
+
20
+ Flat on the ground is the WELL's fill and not the page's. A tile is a recess
21
+ cut into the panel holding it, which is the same shape .axi-well names, and
22
+ spelled --axi-ground it stops being a recess the moment a theme makes that
23
+ panel translucent: an opaque page colour inside a pane of glass is a black
24
+ patch. The outline stays the ink line rather than following the well's,
25
+ because a tile is cut into a SURFACE and an ink edge still has something to
26
+ be darker than there. Same split in .axi-meter and .axi-plot below. */
19
27
  .axi-stat {
20
28
  padding: 12px 14px;
21
- background: var(--axi-ground);
29
+ background: var(--axi-well-fill);
22
30
  border: var(--axi-border-control) solid var(--axi-ink-line);
23
31
  border-radius: var(--axi-radius-sm);
24
32
  }
@@ -105,7 +113,8 @@
105
113
  display: grid; place-items: center;
106
114
  border: var(--axi-border-control) solid var(--axi-ink-line);
107
115
  border-radius: var(--axi-radius-sm);
108
- background: var(--axi-ground);
116
+ /* Flat in the row, like the tile above and the avatar it stands beside. */
117
+ background: var(--axi-well-fill);
109
118
  font: var(--axi-t-micro);
110
119
  color: var(--axi-text-faint);
111
120
  }
@@ -209,7 +218,8 @@
209
218
  .axi-meter {
210
219
  display: flex;
211
220
  height: var(--axi-meter-h, 12px);
212
- background: var(--axi-ground);
221
+ /* The trough is a recess, so it takes the well's fill - see .axi-stat. */
222
+ background: var(--axi-well-fill);
213
223
  border: var(--axi-border-control) solid var(--axi-ink-line);
214
224
  border-radius: var(--axi-radius-sm);
215
225
  overflow: hidden;
@@ -325,7 +335,8 @@
325
335
  .axi-plot {
326
336
  position: relative;
327
337
  height: var(--axi-plot-h, 180px);
328
- background-color: var(--axi-ground);
338
+ /* A frame cut into the panel, not laid on the page - see .axi-stat. */
339
+ background-color: var(--axi-well-fill);
329
340
  background-image: repeating-linear-gradient(
330
341
  to top,
331
342
  var(--axi-rule) 0 var(--axi-border-hairline),
package/src/forms.css CHANGED
@@ -34,7 +34,9 @@
34
34
  height: var(--axi-check-size, 22px);
35
35
  display: inline-grid;
36
36
  place-items: center;
37
- background: var(--axi-ground);
37
+ /* An empty box is a hole you put a mark in, so the level's fill - see
38
+ --axi-well-fill in src/tokens.css. :checked below replaces it outright. */
39
+ background: var(--axi-well-fill);
38
40
  border: var(--axi-border-control) solid var(--axi-ink-line);
39
41
  /* The text-sized step, not the control-sized one - see --axi-radius-xs in
40
42
  src/tokens.css for why a 22px box cannot take a corner scaled for a 36px
@@ -97,13 +97,29 @@ button.axi-panel--tile:hover,
97
97
  padding: var(--axi-well-pad, 10px);
98
98
  }
99
99
 
100
+ /* The reading-scale well, which the comment above describes and this language
101
+ then made every consumer spell out by hand. Saying `--axi-radius-sm` inline
102
+ is fine for the handful of instances that comment imagined; the first real
103
+ consumer to be counted had fifty, and fifty inline style attributes is the
104
+ thing a class exists to prevent. The same argument produced .axi-btn--sm.
105
+
106
+ Only the radius, because that is the only thing that changes: a recess the
107
+ size of a single reading still holds objects that carry their own padding, so
108
+ the pad knob is still the right way to say anything else. */
109
+ .axi-well--sm { --axi-well-radius: var(--axi-radius-sm); }
110
+
100
111
  /* ---------- button ---------- */
101
112
  .axi-btn {
102
113
  display: inline-flex; align-items: center; gap: 8px;
103
114
  padding: var(--axi-btn-pad, 12px 20px);
104
115
  border: var(--axi-border-control) solid var(--axi-ink-line);
105
116
  border-radius: var(--axi-radius-sm);
106
- background: var(--axi-ground);
117
+ /* Flat on its container, which is --axi-well-fill and not the page colour -
118
+ the argument is in src/tokens.css at that token. A button at rest carries
119
+ no block: it is level with the panel holding it and lifts on hover, so the
120
+ resting fill is the level's, and --axi-btn--primary below is the one that
121
+ rests raised and fills with the accent instead. */
122
+ background: var(--axi-well-fill);
107
123
  color: var(--axi-text-dim);
108
124
  font: var(--axi-t-label);
109
125
  letter-spacing: var(--axi-ls-label);
@@ -186,7 +202,8 @@ button.axi-panel--tile:hover,
186
202
  padding: var(--axi-pill-pad, 12px 20px);
187
203
  border: var(--axi-border-control) solid var(--axi-ink-line);
188
204
  border-radius: var(--axi-radius-sm);
189
- background: var(--axi-ground);
205
+ /* The button's fill for the button's reason - see .axi-btn above. */
206
+ background: var(--axi-well-fill);
190
207
  color: var(--axi-text-dim);
191
208
  font: var(--axi-t-label);
192
209
  letter-spacing: var(--axi-ls-label);
@@ -380,7 +397,10 @@ button.axi-panel--tile:hover,
380
397
  .axi-input {
381
398
  width: 100%;
382
399
  padding: var(--axi-input-pad, 11px 12px);
383
- background: var(--axi-ground);
400
+ /* A field is the recessed thing in any bar it sits in - .axi-kbd's comment
401
+ below leans on exactly that - so it takes the level's fill. See
402
+ --axi-well-fill in src/tokens.css. */
403
+ background: var(--axi-well-fill);
384
404
  border: var(--axi-border-control) solid var(--axi-ink-line);
385
405
  border-radius: var(--axi-radius-sm);
386
406
  color: var(--axi-text);
@@ -415,14 +435,15 @@ button.axi-panel--tile:hover,
415
435
  `em`, not `px`: this lands in body copy and in 10px captions alike, and a
416
436
  literal that does not track the text around it reads as a different voice.
417
437
 
418
- It sits on --axi-ground where .axi-kbd sits on --axi-surface, and the
438
+ It sits on --axi-well-fill where .axi-kbd sits on --axi-surface, and the
419
439
  reason is already written down one rule below: a key is raised off what it
420
- is printed on, and a quoted literal is sunk into it. */
440
+ is printed on, and a quoted literal is sunk into it. It said --axi-ground
441
+ for as long as sunk and the page were the same colour; see that token. */
421
442
  .axi-code,
422
443
  .axi-prose code {
423
444
  font-family: var(--axi-mono);
424
445
  font-size: .88em;
425
- background: var(--axi-ground);
446
+ background: var(--axi-well-fill);
426
447
  color: var(--axi-text);
427
448
  border: var(--axi-border-hairline) solid var(--axi-ink-line);
428
449
  border-radius: var(--axi-radius-sm);
@@ -462,7 +483,10 @@ button.axi-panel--tile:hover,
462
483
  padding: 10px 30px 10px 9px;
463
484
  border: var(--axi-border-control) solid var(--axi-ink-line);
464
485
  border-radius: var(--axi-radius-sm);
465
- background-color: var(--axi-ground);
486
+ /* The field's fill, because a closed select is a field - see .axi-input. The
487
+ longhand stays: a caret rides in background-image and the shorthand would
488
+ reset it. */
489
+ background-color: var(--axi-well-fill);
466
490
  color: var(--axi-text-dim);
467
491
  font: var(--axi-t-label);
468
492
  font-size: 12.5px;
@@ -521,8 +545,12 @@ button.axi-panel--tile:hover,
521
545
  letter-spacing: var(--axi-ls-label);
522
546
  text-transform: uppercase;
523
547
  }
548
+ /* A row under the cursor sinks rather than lighting up, so the hover is the
549
+ level's fill - and this popover is filled with --axi-surface-raised, which
550
+ a theme may take translucent, so an opaque row here is the black patch
551
+ --axi-well-fill exists to prevent. Same for the picker's rows below. */
524
552
  .axi-select option:hover, .axi-select option:focus {
525
- background: var(--axi-ground); color: var(--axi-text);
553
+ background: var(--axi-well-fill); color: var(--axi-text);
526
554
  }
527
555
  /* The page-wide focus ring sits 2px outside its element; inside a picker
528
556
  that is 2px into the neighbouring row, so pull it back in. */
@@ -621,7 +649,7 @@ button.axi-panel--tile:hover,
621
649
  font-weight: 900;
622
650
  }
623
651
  .axi-picker__opt:hover, .axi-picker__opt:focus {
624
- background: var(--axi-ground); color: var(--axi-text);
652
+ background: var(--axi-well-fill); color: var(--axi-text);
625
653
  }
626
654
  /* The page-wide focus ring sits 2px outside its element; inside a popover
627
655
  this tight that is 2px into the neighbouring row, so pull it back in. */
@@ -636,7 +664,8 @@ button.axi-panel--tile:hover,
636
664
  who wants round avatars sets --axi-radius-sm and gets rounded controls
637
665
  everywhere - which is the honest version of the request.
638
666
  Flat: outlined, no block. An avatar is content inside a panel, the same as
639
- .axi-stat and .axi-table__rank, not a thing raised off it. */
667
+ .axi-stat and .axi-table__rank, not a thing raised off it - which is what
668
+ --axi-well-fill below is saying, and all three now say it the same way. */
640
669
  .axi-avatar {
641
670
  width: var(--axi-avatar-size, 40px);
642
671
  height: var(--axi-avatar-size, 40px);
@@ -644,7 +673,7 @@ button.axi-panel--tile:hover,
644
673
  display: grid;
645
674
  place-items: center;
646
675
  overflow: hidden;
647
- background: var(--axi-ground);
676
+ background: var(--axi-well-fill);
648
677
  color: var(--axi-text-dim);
649
678
  border: var(--axi-border-control) solid var(--axi-ink-line);
650
679
  border-radius: var(--axi-radius-sm);
package/src/prose.css CHANGED
@@ -48,9 +48,12 @@
48
48
 
49
49
  /* .axi-prose code is declared beside .axi-code in primitives.css: one rule,
50
50
  both selectors, because they are one object. */
51
+ /* A code block and a quote are both fields sunk into the prose around them,
52
+ so both take --axi-well-fill rather than the page colour - the reason is
53
+ written out at .axi-stat in src/data.css. */
51
54
  .axi-prose pre {
52
55
  margin: 0 0 18px; padding: 14px 16px; overflow-x: auto;
53
- background: var(--axi-ground);
56
+ background: var(--axi-well-fill);
54
57
  border: var(--axi-border-control) solid var(--axi-ink-line);
55
58
  border-radius: var(--axi-radius-sm);
56
59
  box-shadow: var(--axi-shadow-control);
@@ -61,7 +64,7 @@
61
64
  margin: 0 0 18px;
62
65
  padding: 11px 13px;
63
66
  border-left: var(--axi-border-panel) solid var(--axi-accent);
64
- background: var(--axi-ground);
67
+ background: var(--axi-well-fill);
65
68
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
66
69
  }
67
70
  .axi-prose blockquote p { margin: 0; font-style: italic; }
package/src/shells.css CHANGED
@@ -13,9 +13,18 @@
13
13
  }
14
14
 
15
15
  /* ---------- masthead ---------- */
16
+ /* Opaque, because content scrolls under it. Which is also why it paints the
17
+ page's light as well as the page's colour: an opaque strip across the top of
18
+ a lit page is a dark band over that light, and the band is there on every
19
+ page of a site the whole time it is open. Attachment fixed, the same as
20
+ `body`, so the strip's share of the light is the share the page would have
21
+ shown there - a wash positioned to this element's own box instead would
22
+ line up with nothing. */
16
23
  .axi-mast {
17
24
  position: sticky; top: 0; z-index: 40;
18
- background: var(--axi-ground);
25
+ background-color: var(--axi-ground);
26
+ background-image: var(--axi-ground-image);
27
+ background-attachment: fixed;
19
28
  border-bottom: var(--axi-border-panel) solid var(--axi-ink-line);
20
29
  }
21
30
  .axi-mast__in {
@@ -226,6 +235,41 @@
226
235
  the faint tone, and inside a filled item it follows the item's ink. */
227
236
  .axi-rail__item .axi-icon { color: var(--axi-text-faint); }
228
237
  .axi-rail__item[aria-current] .axi-icon { color: var(--axi-accent-ink); }
238
+ /* A rail nested inside a panel whose content already spends the accent: a
239
+ metric picker beside the table it drives, a filter list beside its results.
240
+ The language already refuses two fills at two levels inside one rail - that
241
+ is why .axi-rail__subitem has no fill - and a rail that is itself the inner
242
+ level is the same refusal one container further out. Two filled rails on
243
+ one screen name two places, and only one of them is where you are.
244
+
245
+ What it cannot do is borrow the subitem's answer. A subitem is one of a
246
+ handful of leaves under an open category; a picker like this is twenty rows
247
+ and the primary control of its own panel, so brightened text alone loses
248
+ the selection in the list. This is the third weight between the two: the
249
+ row rises off the rail as any hovered row does, and the accent arrives on
250
+ its leading edge. The accent still says which row without the row claiming
251
+ to be the place.
252
+
253
+ The edge is the item's own border, which .axi-rail__item already reserves
254
+ at the control weight and draws transparent - so the bar costs no shadow,
255
+ no extra box and no reflow when it lights up. Logical, not left: this is
256
+ the leading edge, the same correction .axi-rail--flush took.
257
+
258
+ The modifier goes on the list, not on .axi-rail, because a nested picker
259
+ usually has no rail box around it - it sits directly in the panel or well
260
+ that holds it, and inheriting a 208px width and a panel shadow is the
261
+ opposite of what it wants. */
262
+ .axi-rail__nav--quiet .axi-rail__item[aria-current],
263
+ .axi-rail__nav--quiet .axi-rail__item[aria-current]:hover {
264
+ background: var(--axi-surface-raised);
265
+ color: var(--axi-text);
266
+ border-color: transparent;
267
+ border-inline-start-color: var(--axi-accent);
268
+ }
269
+ /* No accent fill here, so the icon has no accent ink to follow - it follows
270
+ the row's own text, the way it does in an unselected item. */
271
+ .axi-rail__nav--quiet .axi-rail__item[aria-current] .axi-icon { color: var(--axi-text); }
272
+
229
273
  /* The second level, indented under the item it belongs to. */
230
274
  .axi-rail__sub { display: flex; flex-direction: column; margin: 3px 0 5px 10px; }
231
275
  .axi-rail__subitem {
@@ -391,7 +435,8 @@
391
435
  padding: 7px 8px; border-radius: var(--axi-radius-sm);
392
436
  font-size: 13px; font-weight: 600; color: var(--axi-text-dim); cursor: pointer;
393
437
  }
394
- .axi-menu__pop label:hover { background: var(--axi-ground); color: var(--axi-text); }
438
+ /* A hovered row sinks - the same fill the select's and the picker's rows use. */
439
+ .axi-menu__pop label:hover { background: var(--axi-well-fill); color: var(--axi-text); }
395
440
  .axi-menu__pop input { margin: 3px 0 0; }
396
441
 
397
442
  /* ---------- command palette ---------- */
@@ -505,7 +550,7 @@
505
550
  .axi-palette__trigger {
506
551
  display: flex; align-items: center; width: 100%; overflow: hidden;
507
552
  padding: 0 10px 0 0;
508
- background: var(--axi-ground);
553
+ background: var(--axi-well-fill);
509
554
  border: var(--axi-border-control) solid var(--axi-ink-line);
510
555
  border-radius: var(--axi-radius-sm);
511
556
  color: var(--axi-text-dim);
@@ -689,7 +734,8 @@
689
734
  width: 32px; height: 32px;
690
735
  display: grid; place-items: center;
691
736
  border-radius: var(--axi-radius-sm);
692
- background: var(--axi-ground);
737
+ /* A control flat on the drawer's head - see --axi-well-fill in tokens. */
738
+ background: var(--axi-well-fill);
693
739
  border: var(--axi-border-control) solid var(--axi-ink-line);
694
740
  color: var(--axi-text-dim);
695
741
  font-size: 15px; font-weight: 900; cursor: pointer;
@@ -755,6 +801,13 @@
755
801
  overflow-y: auto;
756
802
  background-color: var(--axi-ground);
757
803
  background-image: var(--axi-ground-image);
804
+ /* Fixed for both of the reasons `body` is: the light stays put while the
805
+ sheet's own content scrolls, and it is positioned to the viewport rather
806
+ than to this box - which matters here because --axi-sheet-top pushes that
807
+ box down under an app's titlebar, and a wash measured from its top edge
808
+ would sit lower than the one on the page it covers. Opening a sheet would
809
+ nudge the light sideways. */
810
+ background-attachment: fixed;
758
811
  padding: var(--axi-sheet-pad, 12px 16px);
759
812
  }
760
813
  /* The heading the sheet opened with, divided from the body by a rule. Rule 8:
@@ -777,7 +830,8 @@
777
830
  margin: 0;
778
831
  padding: 11px 13px;
779
832
  border-left: var(--axi-border-panel) solid var(--axi-accent);
780
- background: var(--axi-ground);
833
+ /* Sunk into the prose around it, so the well's fill - see .axi-stat. */
834
+ background: var(--axi-well-fill);
781
835
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
782
836
  }
783
837
  .axi-quote :where(p) { margin: 0; font-size: 13px; font-style: italic; color: var(--axi-text-dim); line-height: 1.5; }
@@ -794,13 +848,24 @@
794
848
  takes the panel outline. It carries no block, because a block is an
795
849
  element's shadow on the surface behind it and there is nothing behind a
796
850
  window that this language is entitled to draw on.
851
+
852
+ It is the page. Not a page inside one - the only one, for as long as the app
853
+ is running, which is why it paints the ground's IMAGE as well as the
854
+ ground's colour. `body` is behind it and lights nothing, because a window
855
+ covers the viewport and is opaque by the same requirement that makes it
856
+ opaque over its own content. A window that skipped the image would render a
857
+ theme whose character is the light - glass - as flat near-black: every panel
858
+ inside it correctly translucent, over nothing. Longhands for the reason
859
+ .axi-sheet gives at length below; no attachment, because this element's box
860
+ IS the viewport and never scrolls, so fixed and scroll paint the same pixels.
797
861
  The titlebar is filled with the ink line rather than a surface: it is the
798
862
  outline widened into a strip, which is why the app's content reads as
799
863
  sitting inside the outline instead of under a second toolbar. */
800
864
  .axi-window {
801
865
  height: 100vh;
802
866
  display: flex; flex-direction: column; overflow: hidden;
803
- background: var(--axi-ground);
867
+ background-color: var(--axi-ground);
868
+ background-image: var(--axi-ground-image);
804
869
  border: var(--axi-border-panel) solid var(--axi-ink-line);
805
870
  border-radius: var(--axi-radius);
806
871
  }
package/src/tokens.css CHANGED
@@ -80,7 +80,26 @@
80
80
  against a fill lighter than itself. Every outline in this language is
81
81
  near-black on the assumption of a ground the lines can be darker than; a
82
82
  well IS at ground level, so an ink edge round one is a black line on a
83
- black field. Raised takes ink, sunk takes the rule. */
83
+ black field. Raised takes ink, sunk takes the rule.
84
+
85
+ And what the fill names is not the .axi-well class - it is the level. A
86
+ button at rest, a field, a select, a checkbox, an avatar, a rank, a code
87
+ span, a hovered row in a popover: not one of them is raised off what holds
88
+ it, and every one of them was spelled --axi-ground, because on a theme
89
+ whose surfaces are opaque "flat on its container" and "the page colour"
90
+ are the same colour. They stop being the same colour the instant a theme
91
+ makes the container translucent, which is how the whole set went black at
92
+ once and why this is written here rather than fourteen times downstream.
93
+ Above this level is --axi-surface (.axi-kbd is the one control in the
94
+ language that is genuinely raised, and says so). Below it is --axi-ground
95
+ proper, which now means the page and only the page: body, the sheet, the
96
+ window, the masthead - the things content scrolls under, which need real
97
+ opacity and not a subtraction. Everything between the two is this token.
98
+
99
+ The LINE does not come along. --axi-well-line is for a well cut into the
100
+ page, where there is nothing left for ink to be darker than; a control is
101
+ cut into a SURFACE and its ink edge still has a fill above it to read
102
+ against. Every component below keeps --axi-ink-line. */
84
103
  --axi-well-fill: var(--axi-ground);
85
104
  --axi-well-line: var(--axi-rule);
86
105
  --axi-text: #f4f6f9;