@axiapps/axi-design 1.34.0 → 1.36.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,20 +728,64 @@ 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);
714
743
  padding: 1px 5px;
715
744
  }
716
745
 
746
+ /* ---------- link ---------- */
747
+ /* The same trap .axi-code was pulled out of, one component later. `.axi-prose
748
+ a` was the language's only word for a link, and it is reachable only by
749
+ adopting a whole typography layer - so a consumer with a "learn more" beside
750
+ a setting, or a docs URL in a modal footer, writes its own. One real consumer
751
+ had fifteen of them, hand-spelled, and a third of those were <button>s
752
+ calling an openExternal bridge rather than anchors at all.
753
+
754
+ Both selectors, one rule, for the reason written at .axi-code: a link in
755
+ rendered markdown and a link in interface copy are the same object, and the
756
+ last time this language spelled one object twice the two copies drifted.
757
+ There is a test that they stay one rule.
758
+
759
+ The underline is stated rather than inherited. An <a href> draws one by
760
+ default and a <button> does not, so leaving it to the user agent is exactly
761
+ how the two spellings would come apart - the declaration is a no-op on the
762
+ anchor and load-bearing on the button. Same for the background, border and
763
+ padding resets: they say nothing about an anchor and are the whole reason a
764
+ button can wear this class.
765
+
766
+ `font-family` and `font-size` inherit, but the weight does not: a link is
767
+ 600 wherever it lands, and `font: inherit` would quietly take that away.
768
+
769
+ The hover is written `:where(:hover)` so it weighs one class and the ink
770
+ layer still lands on top - see rule 6's addendum. A link in a caption that
771
+ says `axi-link axi-ink-dim` must stay dim under the cursor, and the plain
772
+ `:hover` form is what made the danger button read white under one. */
773
+ .axi-link,
774
+ .axi-prose a {
775
+ padding: 0;
776
+ background: none;
777
+ border: 0;
778
+ font-family: inherit;
779
+ font-size: inherit;
780
+ font-weight: 600;
781
+ color: var(--axi-accent);
782
+ text-decoration: underline;
783
+ text-underline-offset: 2px;
784
+ cursor: pointer;
785
+ }
786
+ .axi-link:where(:hover),
787
+ .axi-prose a:where(:hover) { color: var(--axi-text); }
788
+
717
789
  /* ---------- keyboard key ---------- */
718
790
  /* A key on the keyboard, named in the interface: the Esc that closes a
719
791
  palette, the Ctrl K that opens it. Drawn as a small control rather than as a
@@ -747,7 +819,10 @@ button.axi-panel--tile:hover,
747
819
  padding: 10px 30px 10px 9px;
748
820
  border: var(--axi-border-control) solid var(--axi-ink-line);
749
821
  border-radius: var(--axi-radius-sm);
750
- background-color: var(--axi-ground);
822
+ /* The field's fill, because a closed select is a field - see .axi-input. The
823
+ longhand stays: a caret rides in background-image and the shorthand would
824
+ reset it. */
825
+ background-color: var(--axi-well-fill);
751
826
  color: var(--axi-text-dim);
752
827
  font: var(--axi-t-label);
753
828
  font-size: 12.5px;
@@ -806,8 +881,12 @@ button.axi-panel--tile:hover,
806
881
  letter-spacing: var(--axi-ls-label);
807
882
  text-transform: uppercase;
808
883
  }
884
+ /* A row under the cursor sinks rather than lighting up, so the hover is the
885
+ level's fill - and this popover is filled with --axi-surface-raised, which
886
+ a theme may take translucent, so an opaque row here is the black patch
887
+ --axi-well-fill exists to prevent. Same for the picker's rows below. */
809
888
  .axi-select option:hover, .axi-select option:focus {
810
- background: var(--axi-ground); color: var(--axi-text);
889
+ background: var(--axi-well-fill); color: var(--axi-text);
811
890
  }
812
891
  /* The page-wide focus ring sits 2px outside its element; inside a picker
813
892
  that is 2px into the neighbouring row, so pull it back in. */
@@ -906,7 +985,7 @@ button.axi-panel--tile:hover,
906
985
  font-weight: 900;
907
986
  }
908
987
  .axi-picker__opt:hover, .axi-picker__opt:focus {
909
- background: var(--axi-ground); color: var(--axi-text);
988
+ background: var(--axi-well-fill); color: var(--axi-text);
910
989
  }
911
990
  /* The page-wide focus ring sits 2px outside its element; inside a popover
912
991
  this tight that is 2px into the neighbouring row, so pull it back in. */
@@ -921,7 +1000,8 @@ button.axi-panel--tile:hover,
921
1000
  who wants round avatars sets --axi-radius-sm and gets rounded controls
922
1001
  everywhere - which is the honest version of the request.
923
1002
  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. */
1003
+ .axi-stat and .axi-table__rank, not a thing raised off it - which is what
1004
+ --axi-well-fill below is saying, and all three now say it the same way. */
925
1005
  .axi-avatar {
926
1006
  width: var(--axi-avatar-size, 40px);
927
1007
  height: var(--axi-avatar-size, 40px);
@@ -929,7 +1009,7 @@ button.axi-panel--tile:hover,
929
1009
  display: grid;
930
1010
  place-items: center;
931
1011
  overflow: hidden;
932
- background: var(--axi-ground);
1012
+ background: var(--axi-well-fill);
933
1013
  color: var(--axi-text-dim);
934
1014
  border: var(--axi-border-control) solid var(--axi-ink-line);
935
1015
  border-radius: var(--axi-radius-sm);
@@ -994,7 +1074,9 @@ button.axi-panel--tile:hover,
994
1074
  height: var(--axi-check-size, 22px);
995
1075
  display: inline-grid;
996
1076
  place-items: center;
997
- background: var(--axi-ground);
1077
+ /* An empty box is a hole you put a mark in, so the level's fill - see
1078
+ --axi-well-fill in src/tokens.css. :checked below replaces it outright. */
1079
+ background: var(--axi-well-fill);
998
1080
  border: var(--axi-border-control) solid var(--axi-ink-line);
999
1081
  /* The text-sized step, not the control-sized one - see --axi-radius-xs in
1000
1082
  src/tokens.css for why a 22px box cannot take a corner scaled for a 36px
@@ -1116,9 +1198,18 @@ textarea.axi-input {
1116
1198
  }
1117
1199
 
1118
1200
  /* ---------- masthead ---------- */
1201
+ /* Opaque, because content scrolls under it. Which is also why it paints the
1202
+ page's light as well as the page's colour: an opaque strip across the top of
1203
+ a lit page is a dark band over that light, and the band is there on every
1204
+ page of a site the whole time it is open. Attachment fixed, the same as
1205
+ `body`, so the strip's share of the light is the share the page would have
1206
+ shown there - a wash positioned to this element's own box instead would
1207
+ line up with nothing. */
1119
1208
  .axi-mast {
1120
1209
  position: sticky; top: 0; z-index: 40;
1121
- background: var(--axi-ground);
1210
+ background-color: var(--axi-ground);
1211
+ background-image: var(--axi-ground-image);
1212
+ background-attachment: fixed;
1122
1213
  border-bottom: var(--axi-border-panel) solid var(--axi-ink-line);
1123
1214
  }
1124
1215
  .axi-mast__in {
@@ -1329,6 +1420,41 @@ textarea.axi-input {
1329
1420
  the faint tone, and inside a filled item it follows the item's ink. */
1330
1421
  .axi-rail__item .axi-icon { color: var(--axi-text-faint); }
1331
1422
  .axi-rail__item[aria-current] .axi-icon { color: var(--axi-accent-ink); }
1423
+ /* A rail nested inside a panel whose content already spends the accent: a
1424
+ metric picker beside the table it drives, a filter list beside its results.
1425
+ The language already refuses two fills at two levels inside one rail - that
1426
+ is why .axi-rail__subitem has no fill - and a rail that is itself the inner
1427
+ level is the same refusal one container further out. Two filled rails on
1428
+ one screen name two places, and only one of them is where you are.
1429
+
1430
+ What it cannot do is borrow the subitem's answer. A subitem is one of a
1431
+ handful of leaves under an open category; a picker like this is twenty rows
1432
+ and the primary control of its own panel, so brightened text alone loses
1433
+ the selection in the list. This is the third weight between the two: the
1434
+ row rises off the rail as any hovered row does, and the accent arrives on
1435
+ its leading edge. The accent still says which row without the row claiming
1436
+ to be the place.
1437
+
1438
+ The edge is the item's own border, which .axi-rail__item already reserves
1439
+ at the control weight and draws transparent - so the bar costs no shadow,
1440
+ no extra box and no reflow when it lights up. Logical, not left: this is
1441
+ the leading edge, the same correction .axi-rail--flush took.
1442
+
1443
+ The modifier goes on the list, not on .axi-rail, because a nested picker
1444
+ usually has no rail box around it - it sits directly in the panel or well
1445
+ that holds it, and inheriting a 208px width and a panel shadow is the
1446
+ opposite of what it wants. */
1447
+ .axi-rail__nav--quiet .axi-rail__item[aria-current],
1448
+ .axi-rail__nav--quiet .axi-rail__item[aria-current]:hover {
1449
+ background: var(--axi-surface-raised);
1450
+ color: var(--axi-text);
1451
+ border-color: transparent;
1452
+ border-inline-start-color: var(--axi-accent);
1453
+ }
1454
+ /* No accent fill here, so the icon has no accent ink to follow - it follows
1455
+ the row's own text, the way it does in an unselected item. */
1456
+ .axi-rail__nav--quiet .axi-rail__item[aria-current] .axi-icon { color: var(--axi-text); }
1457
+
1332
1458
  /* The second level, indented under the item it belongs to. */
1333
1459
  .axi-rail__sub { display: flex; flex-direction: column; margin: 3px 0 5px 10px; }
1334
1460
  .axi-rail__subitem {
@@ -1494,7 +1620,8 @@ textarea.axi-input {
1494
1620
  padding: 7px 8px; border-radius: var(--axi-radius-sm);
1495
1621
  font-size: 13px; font-weight: 600; color: var(--axi-text-dim); cursor: pointer;
1496
1622
  }
1497
- .axi-menu__pop label:hover { background: var(--axi-ground); color: var(--axi-text); }
1623
+ /* A hovered row sinks - the same fill the select's and the picker's rows use. */
1624
+ .axi-menu__pop label:hover { background: var(--axi-well-fill); color: var(--axi-text); }
1498
1625
  .axi-menu__pop input { margin: 3px 0 0; }
1499
1626
 
1500
1627
  /* ---------- command palette ---------- */
@@ -1608,7 +1735,7 @@ textarea.axi-input {
1608
1735
  .axi-palette__trigger {
1609
1736
  display: flex; align-items: center; width: 100%; overflow: hidden;
1610
1737
  padding: 0 10px 0 0;
1611
- background: var(--axi-ground);
1738
+ background: var(--axi-well-fill);
1612
1739
  border: var(--axi-border-control) solid var(--axi-ink-line);
1613
1740
  border-radius: var(--axi-radius-sm);
1614
1741
  color: var(--axi-text-dim);
@@ -1792,7 +1919,8 @@ textarea.axi-input {
1792
1919
  width: 32px; height: 32px;
1793
1920
  display: grid; place-items: center;
1794
1921
  border-radius: var(--axi-radius-sm);
1795
- background: var(--axi-ground);
1922
+ /* A control flat on the drawer's head - see --axi-well-fill in tokens. */
1923
+ background: var(--axi-well-fill);
1796
1924
  border: var(--axi-border-control) solid var(--axi-ink-line);
1797
1925
  color: var(--axi-text-dim);
1798
1926
  font-size: 15px; font-weight: 900; cursor: pointer;
@@ -1858,6 +1986,13 @@ textarea.axi-input {
1858
1986
  overflow-y: auto;
1859
1987
  background-color: var(--axi-ground);
1860
1988
  background-image: var(--axi-ground-image);
1989
+ /* Fixed for both of the reasons `body` is: the light stays put while the
1990
+ sheet's own content scrolls, and it is positioned to the viewport rather
1991
+ than to this box - which matters here because --axi-sheet-top pushes that
1992
+ box down under an app's titlebar, and a wash measured from its top edge
1993
+ would sit lower than the one on the page it covers. Opening a sheet would
1994
+ nudge the light sideways. */
1995
+ background-attachment: fixed;
1861
1996
  padding: var(--axi-sheet-pad, 12px 16px);
1862
1997
  }
1863
1998
  /* The heading the sheet opened with, divided from the body by a rule. Rule 8:
@@ -1880,7 +2015,8 @@ textarea.axi-input {
1880
2015
  margin: 0;
1881
2016
  padding: 11px 13px;
1882
2017
  border-left: var(--axi-border-panel) solid var(--axi-accent);
1883
- background: var(--axi-ground);
2018
+ /* Sunk into the prose around it, so the well's fill - see .axi-stat. */
2019
+ background: var(--axi-well-fill);
1884
2020
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
1885
2021
  }
1886
2022
  .axi-quote :where(p) { margin: 0; font-size: 13px; font-style: italic; color: var(--axi-text-dim); line-height: 1.5; }
@@ -1897,13 +2033,24 @@ textarea.axi-input {
1897
2033
  takes the panel outline. It carries no block, because a block is an
1898
2034
  element's shadow on the surface behind it and there is nothing behind a
1899
2035
  window that this language is entitled to draw on.
2036
+
2037
+ It is the page. Not a page inside one - the only one, for as long as the app
2038
+ is running, which is why it paints the ground's IMAGE as well as the
2039
+ ground's colour. `body` is behind it and lights nothing, because a window
2040
+ covers the viewport and is opaque by the same requirement that makes it
2041
+ opaque over its own content. A window that skipped the image would render a
2042
+ theme whose character is the light - glass - as flat near-black: every panel
2043
+ inside it correctly translucent, over nothing. Longhands for the reason
2044
+ .axi-sheet gives at length below; no attachment, because this element's box
2045
+ IS the viewport and never scrolls, so fixed and scroll paint the same pixels.
1900
2046
  The titlebar is filled with the ink line rather than a surface: it is the
1901
2047
  outline widened into a strip, which is why the app's content reads as
1902
2048
  sitting inside the outline instead of under a second toolbar. */
1903
2049
  .axi-window {
1904
2050
  height: 100vh;
1905
2051
  display: flex; flex-direction: column; overflow: hidden;
1906
- background: var(--axi-ground);
2052
+ background-color: var(--axi-ground);
2053
+ background-image: var(--axi-ground-image);
1907
2054
  border: var(--axi-border-panel) solid var(--axi-ink-line);
1908
2055
  border-radius: var(--axi-radius);
1909
2056
  }
@@ -2170,10 +2317,18 @@ textarea.axi-input {
2170
2317
  an outline and no block - it is content, not something raised off the
2171
2318
  surface it sits on. The number stays in --axi-text unless it has a real
2172
2319
  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. */
2320
+ tile coloured for emphasis is decoration impersonating status.
2321
+
2322
+ Flat on the ground is the WELL's fill and not the page's. A tile is a recess
2323
+ cut into the panel holding it, which is the same shape .axi-well names, and
2324
+ spelled --axi-ground it stops being a recess the moment a theme makes that
2325
+ panel translucent: an opaque page colour inside a pane of glass is a black
2326
+ patch. The outline stays the ink line rather than following the well's,
2327
+ because a tile is cut into a SURFACE and an ink edge still has something to
2328
+ be darker than there. Same split in .axi-meter and .axi-plot below. */
2174
2329
  .axi-stat {
2175
2330
  padding: 12px 14px;
2176
- background: var(--axi-ground);
2331
+ background: var(--axi-well-fill);
2177
2332
  border: var(--axi-border-control) solid var(--axi-ink-line);
2178
2333
  border-radius: var(--axi-radius-sm);
2179
2334
  }
@@ -2260,7 +2415,8 @@ textarea.axi-input {
2260
2415
  display: grid; place-items: center;
2261
2416
  border: var(--axi-border-control) solid var(--axi-ink-line);
2262
2417
  border-radius: var(--axi-radius-sm);
2263
- background: var(--axi-ground);
2418
+ /* Flat in the row, like the tile above and the avatar it stands beside. */
2419
+ background: var(--axi-well-fill);
2264
2420
  font: var(--axi-t-micro);
2265
2421
  color: var(--axi-text-faint);
2266
2422
  }
@@ -2364,7 +2520,8 @@ textarea.axi-input {
2364
2520
  .axi-meter {
2365
2521
  display: flex;
2366
2522
  height: var(--axi-meter-h, 12px);
2367
- background: var(--axi-ground);
2523
+ /* The trough is a recess, so it takes the well's fill - see .axi-stat. */
2524
+ background: var(--axi-well-fill);
2368
2525
  border: var(--axi-border-control) solid var(--axi-ink-line);
2369
2526
  border-radius: var(--axi-radius-sm);
2370
2527
  overflow: hidden;
@@ -2480,7 +2637,8 @@ textarea.axi-input {
2480
2637
  .axi-plot {
2481
2638
  position: relative;
2482
2639
  height: var(--axi-plot-h, 180px);
2483
- background-color: var(--axi-ground);
2640
+ /* A frame cut into the panel, not laid on the page - see .axi-stat. */
2641
+ background-color: var(--axi-well-fill);
2484
2642
  background-image: repeating-linear-gradient(
2485
2643
  to top,
2486
2644
  var(--axi-rule) 0 var(--axi-border-hairline),
@@ -2630,10 +2788,9 @@ textarea.axi-input {
2630
2788
 
2631
2789
  .axi-prose p { margin: 0 0 16px; }
2632
2790
  .axi-prose strong { color: var(--axi-text); font-weight: 800; }
2633
- /* Prose is the one place a link should announce itself: a reader scanning an
2634
- article is looking for them, and inheriting the body ink would hide them. */
2635
- .axi-prose a { color: var(--axi-accent); font-weight: 600; text-underline-offset: 2px; }
2636
- .axi-prose a:hover { color: var(--axi-text); }
2791
+ /* A link is drawn beside .axi-code in primitives.css, in one rule with
2792
+ `.axi-link`, because a link in an article and a link in interface copy are
2793
+ the same object. See the comment there. */
2637
2794
 
2638
2795
  .axi-prose ul, .axi-prose ol { margin: 0 0 16px; padding-left: 22px; }
2639
2796
  .axi-prose li { margin: 0 0 7px; }
@@ -2650,9 +2807,12 @@ textarea.axi-input {
2650
2807
 
2651
2808
  /* .axi-prose code is declared beside .axi-code in primitives.css: one rule,
2652
2809
  both selectors, because they are one object. */
2810
+ /* A code block and a quote are both fields sunk into the prose around them,
2811
+ so both take --axi-well-fill rather than the page colour - the reason is
2812
+ written out at .axi-stat in src/data.css. */
2653
2813
  .axi-prose pre {
2654
2814
  margin: 0 0 18px; padding: 14px 16px; overflow-x: auto;
2655
- background: var(--axi-ground);
2815
+ background: var(--axi-well-fill);
2656
2816
  border: var(--axi-border-control) solid var(--axi-ink-line);
2657
2817
  border-radius: var(--axi-radius-sm);
2658
2818
  box-shadow: var(--axi-shadow-control);
@@ -2663,7 +2823,7 @@ textarea.axi-input {
2663
2823
  margin: 0 0 18px;
2664
2824
  padding: 11px 13px;
2665
2825
  border-left: var(--axi-border-panel) solid var(--axi-accent);
2666
- background: var(--axi-ground);
2826
+ background: var(--axi-well-fill);
2667
2827
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
2668
2828
  }
2669
2829
  .axi-prose blockquote p { margin: 0; font-style: italic; }
package/docs/RULES.md CHANGED
@@ -702,3 +702,48 @@ 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
+ This has now happened twice. `.axi-prose a` was the language's only word for a
707
+ link, and the same consumer had fifteen hand-spelled ones — a third of them
708
+ `<button>`s calling a desktop bridge rather than anchors at all, which is what
709
+ made the drift concrete rather than theoretical: an `<a href>` draws its own
710
+ underline and a `<button>` does not, so the two spellings were not merely
711
+ allowed to come apart, they had already come apart. When you lift a
712
+ layer-scoped style out, look for the declarations the layer was getting for
713
+ free from its element. Those are the ones the new spelling silently loses.
714
+
715
+ Two instances is a pattern, so the check belongs at the top of the list when
716
+ adding anything: grep `src/` for the component's style living behind a layer
717
+ prefix. If it does, it has consumers you cannot see, and they have already
718
+ drawn their own.
719
+
720
+ ### A refusal holds at every level, not just the one it was written for
721
+
722
+ The rail refuses two accent fills inside itself. That is why
723
+ `.axi-rail__subitem` is brightened text and not a second filled row: with two
724
+ fills the reader has to decide which of them answers "where am I", and the
725
+ answer to one question cannot be two things.
726
+
727
+ Written that way, the refusal sounds like it is about indentation. It is not.
728
+ It is about how many times one screen may claim to be a place, and nesting a
729
+ whole rail inside a panel is the same arithmetic as nesting a row inside a
730
+ rail. A stats page with a category rail down the side and a twenty-row metric
731
+ picker inside each section has two rails, both correct on their own, and both
732
+ filled — so the screen makes the claim twice and neither wins. The app that
733
+ hit this had already reasoned its way to the same conclusion and written the
734
+ quiet treatment by hand, in its own stylesheet, with a comment giving exactly
735
+ this reason. Two parties deriving the same rule independently is the signal
736
+ that the rule belongs in the language, not in either party's override file.
737
+
738
+ So when adding a component, take every refusal the neighbouring components
739
+ state and ask what it is really counting. If the answer is "per screen" rather
740
+ than "per box", the component needs a way to stand down — and the modifier is
741
+ cheaper than the override every consumer writes instead. `.axi-rail__nav--quiet`
742
+ is that: the fill goes, the accent stays as the leading edge, and nothing else
743
+ moves.
744
+
745
+ What the modifier must not do is reach for the weaker treatment that already
746
+ exists. A subitem drops the fill *and* the weight, which is right for a few
747
+ leaves under an open category and wrong for a picker that is the primary
748
+ control of its own panel — there, brightened text loses the selection in the
749
+ 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.36.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,20 +435,64 @@ 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);
440
450
  padding: 1px 5px;
441
451
  }
442
452
 
453
+ /* ---------- link ---------- */
454
+ /* The same trap .axi-code was pulled out of, one component later. `.axi-prose
455
+ a` was the language's only word for a link, and it is reachable only by
456
+ adopting a whole typography layer - so a consumer with a "learn more" beside
457
+ a setting, or a docs URL in a modal footer, writes its own. One real consumer
458
+ had fifteen of them, hand-spelled, and a third of those were <button>s
459
+ calling an openExternal bridge rather than anchors at all.
460
+
461
+ Both selectors, one rule, for the reason written at .axi-code: a link in
462
+ rendered markdown and a link in interface copy are the same object, and the
463
+ last time this language spelled one object twice the two copies drifted.
464
+ There is a test that they stay one rule.
465
+
466
+ The underline is stated rather than inherited. An <a href> draws one by
467
+ default and a <button> does not, so leaving it to the user agent is exactly
468
+ how the two spellings would come apart - the declaration is a no-op on the
469
+ anchor and load-bearing on the button. Same for the background, border and
470
+ padding resets: they say nothing about an anchor and are the whole reason a
471
+ button can wear this class.
472
+
473
+ `font-family` and `font-size` inherit, but the weight does not: a link is
474
+ 600 wherever it lands, and `font: inherit` would quietly take that away.
475
+
476
+ The hover is written `:where(:hover)` so it weighs one class and the ink
477
+ layer still lands on top - see rule 6's addendum. A link in a caption that
478
+ says `axi-link axi-ink-dim` must stay dim under the cursor, and the plain
479
+ `:hover` form is what made the danger button read white under one. */
480
+ .axi-link,
481
+ .axi-prose a {
482
+ padding: 0;
483
+ background: none;
484
+ border: 0;
485
+ font-family: inherit;
486
+ font-size: inherit;
487
+ font-weight: 600;
488
+ color: var(--axi-accent);
489
+ text-decoration: underline;
490
+ text-underline-offset: 2px;
491
+ cursor: pointer;
492
+ }
493
+ .axi-link:where(:hover),
494
+ .axi-prose a:where(:hover) { color: var(--axi-text); }
495
+
443
496
  /* ---------- keyboard key ---------- */
444
497
  /* A key on the keyboard, named in the interface: the Esc that closes a
445
498
  palette, the Ctrl K that opens it. Drawn as a small control rather than as a
@@ -473,7 +526,10 @@ button.axi-panel--tile:hover,
473
526
  padding: 10px 30px 10px 9px;
474
527
  border: var(--axi-border-control) solid var(--axi-ink-line);
475
528
  border-radius: var(--axi-radius-sm);
476
- background-color: var(--axi-ground);
529
+ /* The field's fill, because a closed select is a field - see .axi-input. The
530
+ longhand stays: a caret rides in background-image and the shorthand would
531
+ reset it. */
532
+ background-color: var(--axi-well-fill);
477
533
  color: var(--axi-text-dim);
478
534
  font: var(--axi-t-label);
479
535
  font-size: 12.5px;
@@ -532,8 +588,12 @@ button.axi-panel--tile:hover,
532
588
  letter-spacing: var(--axi-ls-label);
533
589
  text-transform: uppercase;
534
590
  }
591
+ /* A row under the cursor sinks rather than lighting up, so the hover is the
592
+ level's fill - and this popover is filled with --axi-surface-raised, which
593
+ a theme may take translucent, so an opaque row here is the black patch
594
+ --axi-well-fill exists to prevent. Same for the picker's rows below. */
535
595
  .axi-select option:hover, .axi-select option:focus {
536
- background: var(--axi-ground); color: var(--axi-text);
596
+ background: var(--axi-well-fill); color: var(--axi-text);
537
597
  }
538
598
  /* The page-wide focus ring sits 2px outside its element; inside a picker
539
599
  that is 2px into the neighbouring row, so pull it back in. */
@@ -632,7 +692,7 @@ button.axi-panel--tile:hover,
632
692
  font-weight: 900;
633
693
  }
634
694
  .axi-picker__opt:hover, .axi-picker__opt:focus {
635
- background: var(--axi-ground); color: var(--axi-text);
695
+ background: var(--axi-well-fill); color: var(--axi-text);
636
696
  }
637
697
  /* The page-wide focus ring sits 2px outside its element; inside a popover
638
698
  this tight that is 2px into the neighbouring row, so pull it back in. */
@@ -647,7 +707,8 @@ button.axi-panel--tile:hover,
647
707
  who wants round avatars sets --axi-radius-sm and gets rounded controls
648
708
  everywhere - which is the honest version of the request.
649
709
  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. */
710
+ .axi-stat and .axi-table__rank, not a thing raised off it - which is what
711
+ --axi-well-fill below is saying, and all three now say it the same way. */
651
712
  .axi-avatar {
652
713
  width: var(--axi-avatar-size, 40px);
653
714
  height: var(--axi-avatar-size, 40px);
@@ -655,7 +716,7 @@ button.axi-panel--tile:hover,
655
716
  display: grid;
656
717
  place-items: center;
657
718
  overflow: hidden;
658
- background: var(--axi-ground);
719
+ background: var(--axi-well-fill);
659
720
  color: var(--axi-text-dim);
660
721
  border: var(--axi-border-control) solid var(--axi-ink-line);
661
722
  border-radius: var(--axi-radius-sm);
package/src/prose.css CHANGED
@@ -28,10 +28,9 @@
28
28
 
29
29
  .axi-prose p { margin: 0 0 16px; }
30
30
  .axi-prose strong { color: var(--axi-text); font-weight: 800; }
31
- /* Prose is the one place a link should announce itself: a reader scanning an
32
- article is looking for them, and inheriting the body ink would hide them. */
33
- .axi-prose a { color: var(--axi-accent); font-weight: 600; text-underline-offset: 2px; }
34
- .axi-prose a:hover { color: var(--axi-text); }
31
+ /* A link is drawn beside .axi-code in primitives.css, in one rule with
32
+ `.axi-link`, because a link in an article and a link in interface copy are
33
+ the same object. See the comment there. */
35
34
 
36
35
  .axi-prose ul, .axi-prose ol { margin: 0 0 16px; padding-left: 22px; }
37
36
  .axi-prose li { margin: 0 0 7px; }
@@ -48,9 +47,12 @@
48
47
 
49
48
  /* .axi-prose code is declared beside .axi-code in primitives.css: one rule,
50
49
  both selectors, because they are one object. */
50
+ /* A code block and a quote are both fields sunk into the prose around them,
51
+ so both take --axi-well-fill rather than the page colour - the reason is
52
+ written out at .axi-stat in src/data.css. */
51
53
  .axi-prose pre {
52
54
  margin: 0 0 18px; padding: 14px 16px; overflow-x: auto;
53
- background: var(--axi-ground);
55
+ background: var(--axi-well-fill);
54
56
  border: var(--axi-border-control) solid var(--axi-ink-line);
55
57
  border-radius: var(--axi-radius-sm);
56
58
  box-shadow: var(--axi-shadow-control);
@@ -61,7 +63,7 @@
61
63
  margin: 0 0 18px;
62
64
  padding: 11px 13px;
63
65
  border-left: var(--axi-border-panel) solid var(--axi-accent);
64
- background: var(--axi-ground);
66
+ background: var(--axi-well-fill);
65
67
  border-radius: 0 var(--axi-radius-sm) var(--axi-radius-sm) 0;
66
68
  }
67
69
  .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;