@axiapps/axi-design 1.34.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;
@@ -388,7 +407,12 @@ button.axi-panel--tile:hover,
388
407
  padding: var(--axi-btn-pad, 12px 20px);
389
408
  border: var(--axi-border-control) solid var(--axi-ink-line);
390
409
  border-radius: var(--axi-radius-sm);
391
- 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);
392
416
  color: var(--axi-text-dim);
393
417
  font: var(--axi-t-label);
394
418
  letter-spacing: var(--axi-ls-label);
@@ -471,7 +495,8 @@ button.axi-panel--tile:hover,
471
495
  padding: var(--axi-pill-pad, 12px 20px);
472
496
  border: var(--axi-border-control) solid var(--axi-ink-line);
473
497
  border-radius: var(--axi-radius-sm);
474
- background: var(--axi-ground);
498
+ /* The button's fill for the button's reason - see .axi-btn above. */
499
+ background: var(--axi-well-fill);
475
500
  color: var(--axi-text-dim);
476
501
  font: var(--axi-t-label);
477
502
  letter-spacing: var(--axi-ls-label);
@@ -665,7 +690,10 @@ button.axi-panel--tile:hover,
665
690
  .axi-input {
666
691
  width: 100%;
667
692
  padding: var(--axi-input-pad, 11px 12px);
668
- 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);
669
697
  border: var(--axi-border-control) solid var(--axi-ink-line);
670
698
  border-radius: var(--axi-radius-sm);
671
699
  color: var(--axi-text);
@@ -700,14 +728,15 @@ button.axi-panel--tile:hover,
700
728
  `em`, not `px`: this lands in body copy and in 10px captions alike, and a
701
729
  literal that does not track the text around it reads as a different voice.
702
730
 
703
- 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
704
732
  reason is already written down one rule below: a key is raised off what it
705
- 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. */
706
735
  .axi-code,
707
736
  .axi-prose code {
708
737
  font-family: var(--axi-mono);
709
738
  font-size: .88em;
710
- background: var(--axi-ground);
739
+ background: var(--axi-well-fill);
711
740
  color: var(--axi-text);
712
741
  border: var(--axi-border-hairline) solid var(--axi-ink-line);
713
742
  border-radius: var(--axi-radius-sm);
@@ -747,7 +776,10 @@ button.axi-panel--tile:hover,
747
776
  padding: 10px 30px 10px 9px;
748
777
  border: var(--axi-border-control) solid var(--axi-ink-line);
749
778
  border-radius: var(--axi-radius-sm);
750
- 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);
751
783
  color: var(--axi-text-dim);
752
784
  font: var(--axi-t-label);
753
785
  font-size: 12.5px;
@@ -806,8 +838,12 @@ button.axi-panel--tile:hover,
806
838
  letter-spacing: var(--axi-ls-label);
807
839
  text-transform: uppercase;
808
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. */
809
845
  .axi-select option:hover, .axi-select option:focus {
810
- background: var(--axi-ground); color: var(--axi-text);
846
+ background: var(--axi-well-fill); color: var(--axi-text);
811
847
  }
812
848
  /* The page-wide focus ring sits 2px outside its element; inside a picker
813
849
  that is 2px into the neighbouring row, so pull it back in. */
@@ -906,7 +942,7 @@ button.axi-panel--tile:hover,
906
942
  font-weight: 900;
907
943
  }
908
944
  .axi-picker__opt:hover, .axi-picker__opt:focus {
909
- background: var(--axi-ground); color: var(--axi-text);
945
+ background: var(--axi-well-fill); color: var(--axi-text);
910
946
  }
911
947
  /* The page-wide focus ring sits 2px outside its element; inside a popover
912
948
  this tight that is 2px into the neighbouring row, so pull it back in. */
@@ -921,7 +957,8 @@ button.axi-panel--tile:hover,
921
957
  who wants round avatars sets --axi-radius-sm and gets rounded controls
922
958
  everywhere - which is the honest version of the request.
923
959
  Flat: outlined, no block. An avatar is content inside a panel, the same as
924
- .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. */
925
962
  .axi-avatar {
926
963
  width: var(--axi-avatar-size, 40px);
927
964
  height: var(--axi-avatar-size, 40px);
@@ -929,7 +966,7 @@ button.axi-panel--tile:hover,
929
966
  display: grid;
930
967
  place-items: center;
931
968
  overflow: hidden;
932
- background: var(--axi-ground);
969
+ background: var(--axi-well-fill);
933
970
  color: var(--axi-text-dim);
934
971
  border: var(--axi-border-control) solid var(--axi-ink-line);
935
972
  border-radius: var(--axi-radius-sm);
@@ -994,7 +1031,9 @@ button.axi-panel--tile:hover,
994
1031
  height: var(--axi-check-size, 22px);
995
1032
  display: inline-grid;
996
1033
  place-items: center;
997
- 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);
998
1037
  border: var(--axi-border-control) solid var(--axi-ink-line);
999
1038
  /* The text-sized step, not the control-sized one - see --axi-radius-xs in
1000
1039
  src/tokens.css for why a 22px box cannot take a corner scaled for a 36px
@@ -1116,9 +1155,18 @@ textarea.axi-input {
1116
1155
  }
1117
1156
 
1118
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. */
1119
1165
  .axi-mast {
1120
1166
  position: sticky; top: 0; z-index: 40;
1121
- background: var(--axi-ground);
1167
+ background-color: var(--axi-ground);
1168
+ background-image: var(--axi-ground-image);
1169
+ background-attachment: fixed;
1122
1170
  border-bottom: var(--axi-border-panel) solid var(--axi-ink-line);
1123
1171
  }
1124
1172
  .axi-mast__in {
@@ -1329,6 +1377,41 @@ textarea.axi-input {
1329
1377
  the faint tone, and inside a filled item it follows the item's ink. */
1330
1378
  .axi-rail__item .axi-icon { color: var(--axi-text-faint); }
1331
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
+
1332
1415
  /* The second level, indented under the item it belongs to. */
1333
1416
  .axi-rail__sub { display: flex; flex-direction: column; margin: 3px 0 5px 10px; }
1334
1417
  .axi-rail__subitem {
@@ -1494,7 +1577,8 @@ textarea.axi-input {
1494
1577
  padding: 7px 8px; border-radius: var(--axi-radius-sm);
1495
1578
  font-size: 13px; font-weight: 600; color: var(--axi-text-dim); cursor: pointer;
1496
1579
  }
1497
- .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); }
1498
1582
  .axi-menu__pop input { margin: 3px 0 0; }
1499
1583
 
1500
1584
  /* ---------- command palette ---------- */
@@ -1608,7 +1692,7 @@ textarea.axi-input {
1608
1692
  .axi-palette__trigger {
1609
1693
  display: flex; align-items: center; width: 100%; overflow: hidden;
1610
1694
  padding: 0 10px 0 0;
1611
- background: var(--axi-ground);
1695
+ background: var(--axi-well-fill);
1612
1696
  border: var(--axi-border-control) solid var(--axi-ink-line);
1613
1697
  border-radius: var(--axi-radius-sm);
1614
1698
  color: var(--axi-text-dim);
@@ -1792,7 +1876,8 @@ textarea.axi-input {
1792
1876
  width: 32px; height: 32px;
1793
1877
  display: grid; place-items: center;
1794
1878
  border-radius: var(--axi-radius-sm);
1795
- 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);
1796
1881
  border: var(--axi-border-control) solid var(--axi-ink-line);
1797
1882
  color: var(--axi-text-dim);
1798
1883
  font-size: 15px; font-weight: 900; cursor: pointer;
@@ -1858,6 +1943,13 @@ textarea.axi-input {
1858
1943
  overflow-y: auto;
1859
1944
  background-color: var(--axi-ground);
1860
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;
1861
1953
  padding: var(--axi-sheet-pad, 12px 16px);
1862
1954
  }
1863
1955
  /* The heading the sheet opened with, divided from the body by a rule. Rule 8:
@@ -1880,7 +1972,8 @@ textarea.axi-input {
1880
1972
  margin: 0;
1881
1973
  padding: 11px 13px;
1882
1974
  border-left: var(--axi-border-panel) solid var(--axi-accent);
1883
- 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);
1884
1977
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
1885
1978
  }
1886
1979
  .axi-quote :where(p) { margin: 0; font-size: 13px; font-style: italic; color: var(--axi-text-dim); line-height: 1.5; }
@@ -1897,13 +1990,24 @@ textarea.axi-input {
1897
1990
  takes the panel outline. It carries no block, because a block is an
1898
1991
  element's shadow on the surface behind it and there is nothing behind a
1899
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.
1900
2003
  The titlebar is filled with the ink line rather than a surface: it is the
1901
2004
  outline widened into a strip, which is why the app's content reads as
1902
2005
  sitting inside the outline instead of under a second toolbar. */
1903
2006
  .axi-window {
1904
2007
  height: 100vh;
1905
2008
  display: flex; flex-direction: column; overflow: hidden;
1906
- background: var(--axi-ground);
2009
+ background-color: var(--axi-ground);
2010
+ background-image: var(--axi-ground-image);
1907
2011
  border: var(--axi-border-panel) solid var(--axi-ink-line);
1908
2012
  border-radius: var(--axi-radius);
1909
2013
  }
@@ -2170,10 +2274,18 @@ textarea.axi-input {
2170
2274
  an outline and no block - it is content, not something raised off the
2171
2275
  surface it sits on. The number stays in --axi-text unless it has a real
2172
2276
  state: rule 5 applies to a figure exactly as it applies to a chip, and a
2173
- 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. */
2174
2286
  .axi-stat {
2175
2287
  padding: 12px 14px;
2176
- background: var(--axi-ground);
2288
+ background: var(--axi-well-fill);
2177
2289
  border: var(--axi-border-control) solid var(--axi-ink-line);
2178
2290
  border-radius: var(--axi-radius-sm);
2179
2291
  }
@@ -2260,7 +2372,8 @@ textarea.axi-input {
2260
2372
  display: grid; place-items: center;
2261
2373
  border: var(--axi-border-control) solid var(--axi-ink-line);
2262
2374
  border-radius: var(--axi-radius-sm);
2263
- 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);
2264
2377
  font: var(--axi-t-micro);
2265
2378
  color: var(--axi-text-faint);
2266
2379
  }
@@ -2364,7 +2477,8 @@ textarea.axi-input {
2364
2477
  .axi-meter {
2365
2478
  display: flex;
2366
2479
  height: var(--axi-meter-h, 12px);
2367
- 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);
2368
2482
  border: var(--axi-border-control) solid var(--axi-ink-line);
2369
2483
  border-radius: var(--axi-radius-sm);
2370
2484
  overflow: hidden;
@@ -2480,7 +2594,8 @@ textarea.axi-input {
2480
2594
  .axi-plot {
2481
2595
  position: relative;
2482
2596
  height: var(--axi-plot-h, 180px);
2483
- 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);
2484
2599
  background-image: repeating-linear-gradient(
2485
2600
  to top,
2486
2601
  var(--axi-rule) 0 var(--axi-border-hairline),
@@ -2650,9 +2765,12 @@ textarea.axi-input {
2650
2765
 
2651
2766
  /* .axi-prose code is declared beside .axi-code in primitives.css: one rule,
2652
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. */
2653
2771
  .axi-prose pre {
2654
2772
  margin: 0 0 18px; padding: 14px 16px; overflow-x: auto;
2655
- background: var(--axi-ground);
2773
+ background: var(--axi-well-fill);
2656
2774
  border: var(--axi-border-control) solid var(--axi-ink-line);
2657
2775
  border-radius: var(--axi-radius-sm);
2658
2776
  box-shadow: var(--axi-shadow-control);
@@ -2663,7 +2781,7 @@ textarea.axi-input {
2663
2781
  margin: 0 0 18px;
2664
2782
  padding: 11px 13px;
2665
2783
  border-left: var(--axi-border-panel) solid var(--axi-accent);
2666
- background: var(--axi-ground);
2784
+ background: var(--axi-well-fill);
2667
2785
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
2668
2786
  }
2669
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.34.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
@@ -114,7 +114,12 @@ button.axi-panel--tile:hover,
114
114
  padding: var(--axi-btn-pad, 12px 20px);
115
115
  border: var(--axi-border-control) solid var(--axi-ink-line);
116
116
  border-radius: var(--axi-radius-sm);
117
- 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);
118
123
  color: var(--axi-text-dim);
119
124
  font: var(--axi-t-label);
120
125
  letter-spacing: var(--axi-ls-label);
@@ -197,7 +202,8 @@ button.axi-panel--tile:hover,
197
202
  padding: var(--axi-pill-pad, 12px 20px);
198
203
  border: var(--axi-border-control) solid var(--axi-ink-line);
199
204
  border-radius: var(--axi-radius-sm);
200
- background: var(--axi-ground);
205
+ /* The button's fill for the button's reason - see .axi-btn above. */
206
+ background: var(--axi-well-fill);
201
207
  color: var(--axi-text-dim);
202
208
  font: var(--axi-t-label);
203
209
  letter-spacing: var(--axi-ls-label);
@@ -391,7 +397,10 @@ button.axi-panel--tile:hover,
391
397
  .axi-input {
392
398
  width: 100%;
393
399
  padding: var(--axi-input-pad, 11px 12px);
394
- 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);
395
404
  border: var(--axi-border-control) solid var(--axi-ink-line);
396
405
  border-radius: var(--axi-radius-sm);
397
406
  color: var(--axi-text);
@@ -426,14 +435,15 @@ button.axi-panel--tile:hover,
426
435
  `em`, not `px`: this lands in body copy and in 10px captions alike, and a
427
436
  literal that does not track the text around it reads as a different voice.
428
437
 
429
- 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
430
439
  reason is already written down one rule below: a key is raised off what it
431
- 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. */
432
442
  .axi-code,
433
443
  .axi-prose code {
434
444
  font-family: var(--axi-mono);
435
445
  font-size: .88em;
436
- background: var(--axi-ground);
446
+ background: var(--axi-well-fill);
437
447
  color: var(--axi-text);
438
448
  border: var(--axi-border-hairline) solid var(--axi-ink-line);
439
449
  border-radius: var(--axi-radius-sm);
@@ -473,7 +483,10 @@ button.axi-panel--tile:hover,
473
483
  padding: 10px 30px 10px 9px;
474
484
  border: var(--axi-border-control) solid var(--axi-ink-line);
475
485
  border-radius: var(--axi-radius-sm);
476
- 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);
477
490
  color: var(--axi-text-dim);
478
491
  font: var(--axi-t-label);
479
492
  font-size: 12.5px;
@@ -532,8 +545,12 @@ button.axi-panel--tile:hover,
532
545
  letter-spacing: var(--axi-ls-label);
533
546
  text-transform: uppercase;
534
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. */
535
552
  .axi-select option:hover, .axi-select option:focus {
536
- background: var(--axi-ground); color: var(--axi-text);
553
+ background: var(--axi-well-fill); color: var(--axi-text);
537
554
  }
538
555
  /* The page-wide focus ring sits 2px outside its element; inside a picker
539
556
  that is 2px into the neighbouring row, so pull it back in. */
@@ -632,7 +649,7 @@ button.axi-panel--tile:hover,
632
649
  font-weight: 900;
633
650
  }
634
651
  .axi-picker__opt:hover, .axi-picker__opt:focus {
635
- background: var(--axi-ground); color: var(--axi-text);
652
+ background: var(--axi-well-fill); color: var(--axi-text);
636
653
  }
637
654
  /* The page-wide focus ring sits 2px outside its element; inside a popover
638
655
  this tight that is 2px into the neighbouring row, so pull it back in. */
@@ -647,7 +664,8 @@ button.axi-panel--tile:hover,
647
664
  who wants round avatars sets --axi-radius-sm and gets rounded controls
648
665
  everywhere - which is the honest version of the request.
649
666
  Flat: outlined, no block. An avatar is content inside a panel, the same as
650
- .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. */
651
669
  .axi-avatar {
652
670
  width: var(--axi-avatar-size, 40px);
653
671
  height: var(--axi-avatar-size, 40px);
@@ -655,7 +673,7 @@ button.axi-panel--tile:hover,
655
673
  display: grid;
656
674
  place-items: center;
657
675
  overflow: hidden;
658
- background: var(--axi-ground);
676
+ background: var(--axi-well-fill);
659
677
  color: var(--axi-text-dim);
660
678
  border: var(--axi-border-control) solid var(--axi-ink-line);
661
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;