@axiapps/axi-design 1.15.0 → 1.17.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/README.md CHANGED
@@ -138,6 +138,9 @@ not "what does the system look like". Everything else is
138
138
  | `--axi-check-size` | the size of an `.axi-check` or `.axi-radio` box | `22px` | `<input type="checkbox" class="axi-check" style="--axi-check-size: 16px">` |
139
139
  | `--axi-check-fill` | the fill a checked `.axi-check` takes, and the colour of a checked `.axi-radio`'s diamond | `var(--axi-accent)` | `<input type="checkbox" class="axi-check" style="--axi-check-fill: var(--axi-danger)">` |
140
140
  | `--axi-textarea-h` | the minimum height of a `<textarea class="axi-input">` | `90px` | `<textarea class="axi-input" style="--axi-textarea-h: 200px">` |
141
+ | `--axi-well-pad` | the padding inside an `.axi-well` | `10px` | `<div class="axi-well" style="--axi-well-pad: 18px">` |
142
+ | `--axi-rail-w` | an `.axi-rail`'s width, for labels longer than the default holds | `208px` | `<aside class="axi-rail" style="--axi-rail-w: 260px">` |
143
+ | `--axi-rail-pad` | the padding inside an `.axi-rail` | `10px` | `<aside class="axi-rail" style="--axi-rail-pad: 6px">` |
141
144
  | `--axi-avatar-size` | the size of an `.axi-avatar` square | `40px` | `<span class="axi-avatar" style="--axi-avatar-size: 28px">MS</span>` |
142
145
  | `--axi-modal-width` | the maximum width of an `.axi-modal`, before the viewport clamp | `560px` | `<dialog class="axi-modal" style="--axi-modal-width: 760px">` |
143
146
  | `--axi-card-strip` | the colour of `.axi-card--strip`'s top strip | `var(--axi-accent)` | `<a class="axi-card axi-card--strip" style="--axi-card-strip: var(--axi-ok)">` |
package/dist/axi.css CHANGED
@@ -43,6 +43,28 @@
43
43
  --axi-ink-on-fill below, on the other side of the same token. */
44
44
  --axi-ground-deep: var(--axi-ink-line);
45
45
  --axi-rule: #3a4250; /* internal rules, inside an outlined panel */
46
+ /* --- the well --- */
47
+ /* A field sunk INTO a surface rather than raised off one: a table picker's
48
+ list, a bar's trough, a column of cards standing on the page. The language
49
+ had no name for it, and every consumer that needed one reached for a second
50
+ .axi-panel inside the first - two identical fills parted by a line, which
51
+ reads as one field with a stray rule through it.
52
+
53
+ Two tokens because a well is the exact inverse of a panel on both counts.
54
+ The fill is a SUBTRACTION from whatever is behind it, not a colour of its
55
+ own: spelled as --axi-ground it is right in this theme, where surfaces are
56
+ opaque and the page really is what shows through a hole in one, and wrong
57
+ in a glass theme, where an opaque page colour inside a translucent pane is
58
+ a black patch rather than a recess. A theme restates it as a darkening at
59
+ alpha and the panel's own tint carries through.
60
+
61
+ The line is --axi-rule and not --axi-ink-line, because ink only reads
62
+ against a fill lighter than itself. Every outline in this language is
63
+ near-black on the assumption of a ground the lines can be darker than; a
64
+ well IS at ground level, so an ink edge round one is a black line on a
65
+ black field. Raised takes ink, sunk takes the rule. */
66
+ --axi-well-fill: var(--axi-ground);
67
+ --axi-well-line: var(--axi-rule);
46
68
  --axi-text: #f4f6f9;
47
69
  --axi-text-dim: #a7b0be;
48
70
  --axi-text-faint: #7c8695;
@@ -251,6 +273,22 @@ a { color: inherit; }
251
273
  padding: var(--axi-panel-pad, 26px);
252
274
  }
253
275
 
276
+ /* ---------- well ---------- */
277
+ /* The panel's inverse: a field sunk into the surface around it rather than
278
+ raised off it. No block, because a recess casts nothing, and the rule rather
279
+ than the ink for its edge - see --axi-well-fill in src/tokens.css for why
280
+ both of those follow from the same fact. Reach for this wherever a second
281
+ panel inside a panel was the instinct: a picker list beside a table, a column
282
+ of cards standing on the page, a trough a bar fills. */
283
+ .axi-well {
284
+ background: var(--axi-well-fill);
285
+ border: var(--axi-border-control) solid var(--axi-well-line);
286
+ border-radius: var(--axi-radius);
287
+ /* Tighter than a panel's 26px: a well is a container for objects that carry
288
+ their own padding, not a page margin. */
289
+ padding: var(--axi-well-pad, 10px);
290
+ }
291
+
254
292
  /* ---------- button ---------- */
255
293
  .axi-btn {
256
294
  display: inline-flex; align-items: center; gap: 8px;
@@ -874,23 +912,167 @@ textarea.axi-input {
874
912
  color: var(--axi-text-faint);
875
913
  letter-spacing: .09em; text-transform: uppercase;
876
914
  }
877
- .axi-tabs { display: flex; gap: 8px; margin-left: auto; }
878
- .axi-tabs a {
915
+ /* Anchors and buttons both. A strip that moves between URLs is made of links;
916
+ a strip inside an application that swaps a view in place is made of buttons,
917
+ and they look the same because they answer the same question. A button also
918
+ arrives carrying the UA's own font, fill and cursor, which an anchor does
919
+ not, so those three are cleared here rather than left to the consumer. */
920
+ .axi-tabs { display: flex; gap: 8px; }
921
+ /* The auto margin belongs to the masthead, not to the strip: it is a statement
922
+ about where the tabs sit in that row, and a strip standing on its own -
923
+ spanning a view instead of ending a header - has nothing to push away from. */
924
+ .axi-mast .axi-tabs { margin-left: auto; }
925
+ .axi-tabs a,
926
+ .axi-tabs button {
879
927
  padding: 8px 16px;
880
928
  font: 800 13.5px/1 var(--axi-sans);
881
929
  color: var(--axi-text-dim); text-decoration: none;
930
+ background: none;
931
+ cursor: pointer;
882
932
  border: var(--axi-border-control) solid transparent;
883
933
  border-radius: var(--axi-radius-sm);
884
934
  }
885
- .axi-tabs a:hover { color: var(--axi-text); border-color: var(--axi-rule); }
935
+ .axi-tabs a:hover,
936
+ .axi-tabs button:hover { color: var(--axi-text); border-color: var(--axi-rule); }
886
937
  /* The current tab is filled and blocked - the same treatment a pressed pill
887
938
  gets, because it is the same idea: this one is on. */
888
- .axi-tabs a[aria-current="page"] {
939
+ .axi-tabs a[aria-current="page"],
940
+ .axi-tabs button[aria-current="page"] {
941
+ background: var(--axi-accent);
942
+ color: var(--axi-accent-ink);
943
+ border-color: var(--axi-ink-line);
944
+ box-shadow: var(--axi-shadow-control);
945
+ }
946
+
947
+ /* A closable tab - a report or a document, an open thing rather than a place.
948
+ The label and its close control travel together, so the pair is a box and
949
+ the strip's gap falls between pairs instead of between a tab and its own X.
950
+
951
+ The close control is a tab's affordance and not a tab, so it opts out of the
952
+ tab treatment: no tab padding, no tab weight, and faint until the cursor is
953
+ on it. Two classes to outrank `.axi-tabs button`, which is one class and one
954
+ element and would otherwise win on source order alone. */
955
+ /* A document title is arbitrarily long, so the label truncates rather than
956
+ pushing the close control off the strip. Both boxes need min-width: 0 - a
957
+ flex item's default min-width is its content, so without it the label simply
958
+ refuses to shrink and the ellipsis never appears. The consumer sets the width
959
+ it wants to hold on .axi-tabs__tab. */
960
+ .axi-tabs__tab { display: inline-flex; align-items: center; min-width: 0; }
961
+ .axi-tabs__tab > a:first-child,
962
+ .axi-tabs__tab > button:first-child {
963
+ min-width: 0;
964
+ overflow: hidden;
965
+ text-overflow: ellipsis;
966
+ white-space: nowrap;
967
+ }
968
+ .axi-tabs .axi-tabs__close {
969
+ padding: 0 6px;
970
+ font: var(--axi-t-micro);
971
+ color: var(--axi-text-faint);
972
+ border-color: transparent;
973
+ }
974
+ .axi-tabs .axi-tabs__close:hover { color: var(--axi-text); border-color: transparent; }
975
+
976
+ /* ---------- rail ---------- */
977
+ /* Vertical navigation, for a set too large for a tab strip to hold. Same
978
+ grammar as .axi-tabs deliberately - the item you are on is filled with the
979
+ accent and blocked, the rest are transparent until hovered - because "where
980
+ am I" is one question and should have one answer however the app happens to
981
+ be laying it out.
982
+
983
+ What a rail adds over a strip is a second level. A category is a place and
984
+ takes the fill; a section under it is where the page is scrolled to, which is
985
+ a smaller claim and is drawn as one: brightened text against the dim list
986
+ around it, no fill, no edge. Two fills at two levels would leave the reader
987
+ deciding which of them is the answer.
988
+
989
+ --axi-rail-w is declared nowhere and read with a fallback, i.e. it is the hook
990
+ a consumer overrides to widen the rail for longer labels. */
991
+ .axi-rail {
992
+ display: flex;
993
+ flex-direction: column;
994
+ width: var(--axi-rail-w, 208px);
995
+ flex: none;
996
+ overflow-y: auto;
997
+ padding: var(--axi-rail-pad, 10px);
998
+ background: var(--axi-surface);
999
+ backdrop-filter: var(--axi-surface-filter);
1000
+ border: var(--axi-border-panel) solid var(--axi-ink-line);
1001
+ border-radius: var(--axi-radius);
1002
+ }
1003
+ /* A rail that IS the edge of the page rather than a panel standing in the
1004
+ layout - pinned to the viewport, or a drawer's contents. It keeps the one
1005
+ border that faces the content and drops everything that says "this is an
1006
+ object with space around it": no radius on the flush side, and no block,
1007
+ because a block needs somewhere to fall.
1008
+
1009
+ The block is stated as what a free-standing rail has rather than as
1010
+ something the flush one turns off, so there is exactly one box-shadow here
1011
+ and it names the panel block. `box-shadow: none` would be a second spelling
1012
+ of the raise for a theme to miss. */
1013
+ .axi-rail:not(.axi-rail--flush) { box-shadow: var(--axi-shadow-panel); }
1014
+ .axi-rail--flush {
1015
+ border: 0;
1016
+ border-right: var(--axi-border-panel) solid var(--axi-ink-line);
1017
+ border-radius: 0;
1018
+ }
1019
+ .axi-rail__nav { display: flex; flex-direction: column; gap: 2px; }
1020
+ .axi-rail__item {
1021
+ display: flex;
1022
+ align-items: center;
1023
+ gap: 10px;
1024
+ width: 100%;
1025
+ padding: 8px 10px;
1026
+ font: 800 12.5px/1 var(--axi-sans);
1027
+ color: var(--axi-text-dim);
1028
+ text-align: left;
1029
+ background: transparent;
1030
+ border: var(--axi-border-control) solid transparent;
1031
+ border-radius: var(--axi-radius-sm);
1032
+ cursor: pointer;
1033
+ }
1034
+ .axi-rail__item:hover { color: var(--axi-text); background: var(--axi-surface-raised); }
1035
+ /* The same treatment a pressed pill and a current tab get. Only ever one of
1036
+ these on screen. Any aria-current value, because the right one depends on
1037
+ what the rail navigates - `page` for a view, `location` for a place within
1038
+ one - and the fill means the same thing either way. */
1039
+ .axi-rail__item[aria-current],
1040
+ .axi-rail__item[aria-current]:hover {
889
1041
  background: var(--axi-accent);
890
1042
  color: var(--axi-accent-ink);
891
1043
  border-color: var(--axi-ink-line);
892
1044
  box-shadow: var(--axi-shadow-control);
893
1045
  }
1046
+ /* An icon beside a label is a label for it, not a second accent - so it takes
1047
+ the faint tone, and inside a filled item it follows the item's ink. */
1048
+ .axi-rail__item .axi-icon { color: var(--axi-text-faint); }
1049
+ .axi-rail__item[aria-current] .axi-icon { color: var(--axi-accent-ink); }
1050
+ /* The second level, indented under the item it belongs to. */
1051
+ .axi-rail__sub { display: flex; flex-direction: column; margin: 3px 0 5px 10px; }
1052
+ .axi-rail__subitem {
1053
+ padding: 5px 8px;
1054
+ font: 700 11.5px/1.3 var(--axi-sans);
1055
+ text-align: left;
1056
+ color: var(--axi-text-faint);
1057
+ background: transparent;
1058
+ border: 0;
1059
+ border-radius: var(--axi-radius-sm);
1060
+ cursor: pointer;
1061
+ }
1062
+ .axi-rail__subitem:hover { color: var(--axi-text-dim); background: var(--axi-surface-raised); }
1063
+ .axi-rail__subitem[aria-current] { color: var(--axi-text); }
1064
+ /* An annotation on an item, not a measurement of it, so rule 5 draws it
1065
+ outlined rather than filled. */
1066
+ .axi-rail__mark {
1067
+ margin-left: auto;
1068
+ padding: 2px 5px;
1069
+ font: var(--axi-t-micro);
1070
+ letter-spacing: .04em;
1071
+ text-transform: uppercase;
1072
+ color: var(--axi-meta);
1073
+ border: var(--axi-border-hairline) solid var(--axi-meta);
1074
+ border-radius: var(--axi-radius-sm);
1075
+ }
894
1076
 
895
1077
  /* ---------- notice ---------- */
896
1078
  .axi-notice {
@@ -1205,7 +1387,8 @@ textarea.axi-input {
1205
1387
 
1206
1388
  @media (max-width: 640px) {
1207
1389
  .axi-mast__in { min-height: 0; padding: 10px 16px; gap: 12px; }
1208
- .axi-tabs { margin-left: 0; width: 100%; overflow-x: auto; }
1390
+ .axi-mast .axi-tabs { margin-left: 0; }
1391
+ .axi-tabs { width: 100%; overflow-x: auto; }
1209
1392
  /* The offset blocks eat horizontal room a phone does not have, so the panel
1210
1393
  step drops to the control step here rather than to an invented number.
1211
1394
  The card's hover has to come down with it - left alone it kept deepening
@@ -10,6 +10,7 @@
10
10
  --axi-ink-line: rgba(180, 200, 255, .16);
11
11
  --axi-rule: rgba(180, 200, 255, .14);
12
12
  --axi-ground-deep: #0c0e12;
13
+ --axi-well-fill: rgba(6, 8, 14, .32);
13
14
  --axi-accent-ink: #0c0e12;
14
15
  --axi-ink-on-fill: #0c0e12;
15
16
  --axi-text: #f2f5fb;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiapps/axi-design",
3
- "version": "1.15.0",
3
+ "version": "1.17.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",
@@ -29,6 +29,22 @@
29
29
  padding: var(--axi-panel-pad, 26px);
30
30
  }
31
31
 
32
+ /* ---------- well ---------- */
33
+ /* The panel's inverse: a field sunk into the surface around it rather than
34
+ raised off it. No block, because a recess casts nothing, and the rule rather
35
+ than the ink for its edge - see --axi-well-fill in src/tokens.css for why
36
+ both of those follow from the same fact. Reach for this wherever a second
37
+ panel inside a panel was the instinct: a picker list beside a table, a column
38
+ of cards standing on the page, a trough a bar fills. */
39
+ .axi-well {
40
+ background: var(--axi-well-fill);
41
+ border: var(--axi-border-control) solid var(--axi-well-line);
42
+ border-radius: var(--axi-radius);
43
+ /* Tighter than a panel's 26px: a well is a container for objects that carry
44
+ their own padding, not a page margin. */
45
+ padding: var(--axi-well-pad, 10px);
46
+ }
47
+
32
48
  /* ---------- button ---------- */
33
49
  .axi-btn {
34
50
  display: inline-flex; align-items: center; gap: 8px;
package/src/shells.css CHANGED
@@ -48,23 +48,167 @@
48
48
  color: var(--axi-text-faint);
49
49
  letter-spacing: .09em; text-transform: uppercase;
50
50
  }
51
- .axi-tabs { display: flex; gap: 8px; margin-left: auto; }
52
- .axi-tabs a {
51
+ /* Anchors and buttons both. A strip that moves between URLs is made of links;
52
+ a strip inside an application that swaps a view in place is made of buttons,
53
+ and they look the same because they answer the same question. A button also
54
+ arrives carrying the UA's own font, fill and cursor, which an anchor does
55
+ not, so those three are cleared here rather than left to the consumer. */
56
+ .axi-tabs { display: flex; gap: 8px; }
57
+ /* The auto margin belongs to the masthead, not to the strip: it is a statement
58
+ about where the tabs sit in that row, and a strip standing on its own -
59
+ spanning a view instead of ending a header - has nothing to push away from. */
60
+ .axi-mast .axi-tabs { margin-left: auto; }
61
+ .axi-tabs a,
62
+ .axi-tabs button {
53
63
  padding: 8px 16px;
54
64
  font: 800 13.5px/1 var(--axi-sans);
55
65
  color: var(--axi-text-dim); text-decoration: none;
66
+ background: none;
67
+ cursor: pointer;
56
68
  border: var(--axi-border-control) solid transparent;
57
69
  border-radius: var(--axi-radius-sm);
58
70
  }
59
- .axi-tabs a:hover { color: var(--axi-text); border-color: var(--axi-rule); }
71
+ .axi-tabs a:hover,
72
+ .axi-tabs button:hover { color: var(--axi-text); border-color: var(--axi-rule); }
60
73
  /* The current tab is filled and blocked - the same treatment a pressed pill
61
74
  gets, because it is the same idea: this one is on. */
62
- .axi-tabs a[aria-current="page"] {
75
+ .axi-tabs a[aria-current="page"],
76
+ .axi-tabs button[aria-current="page"] {
77
+ background: var(--axi-accent);
78
+ color: var(--axi-accent-ink);
79
+ border-color: var(--axi-ink-line);
80
+ box-shadow: var(--axi-shadow-control);
81
+ }
82
+
83
+ /* A closable tab - a report or a document, an open thing rather than a place.
84
+ The label and its close control travel together, so the pair is a box and
85
+ the strip's gap falls between pairs instead of between a tab and its own X.
86
+
87
+ The close control is a tab's affordance and not a tab, so it opts out of the
88
+ tab treatment: no tab padding, no tab weight, and faint until the cursor is
89
+ on it. Two classes to outrank `.axi-tabs button`, which is one class and one
90
+ element and would otherwise win on source order alone. */
91
+ /* A document title is arbitrarily long, so the label truncates rather than
92
+ pushing the close control off the strip. Both boxes need min-width: 0 - a
93
+ flex item's default min-width is its content, so without it the label simply
94
+ refuses to shrink and the ellipsis never appears. The consumer sets the width
95
+ it wants to hold on .axi-tabs__tab. */
96
+ .axi-tabs__tab { display: inline-flex; align-items: center; min-width: 0; }
97
+ .axi-tabs__tab > a:first-child,
98
+ .axi-tabs__tab > button:first-child {
99
+ min-width: 0;
100
+ overflow: hidden;
101
+ text-overflow: ellipsis;
102
+ white-space: nowrap;
103
+ }
104
+ .axi-tabs .axi-tabs__close {
105
+ padding: 0 6px;
106
+ font: var(--axi-t-micro);
107
+ color: var(--axi-text-faint);
108
+ border-color: transparent;
109
+ }
110
+ .axi-tabs .axi-tabs__close:hover { color: var(--axi-text); border-color: transparent; }
111
+
112
+ /* ---------- rail ---------- */
113
+ /* Vertical navigation, for a set too large for a tab strip to hold. Same
114
+ grammar as .axi-tabs deliberately - the item you are on is filled with the
115
+ accent and blocked, the rest are transparent until hovered - because "where
116
+ am I" is one question and should have one answer however the app happens to
117
+ be laying it out.
118
+
119
+ What a rail adds over a strip is a second level. A category is a place and
120
+ takes the fill; a section under it is where the page is scrolled to, which is
121
+ a smaller claim and is drawn as one: brightened text against the dim list
122
+ around it, no fill, no edge. Two fills at two levels would leave the reader
123
+ deciding which of them is the answer.
124
+
125
+ --axi-rail-w is declared nowhere and read with a fallback, i.e. it is the hook
126
+ a consumer overrides to widen the rail for longer labels. */
127
+ .axi-rail {
128
+ display: flex;
129
+ flex-direction: column;
130
+ width: var(--axi-rail-w, 208px);
131
+ flex: none;
132
+ overflow-y: auto;
133
+ padding: var(--axi-rail-pad, 10px);
134
+ background: var(--axi-surface);
135
+ backdrop-filter: var(--axi-surface-filter);
136
+ border: var(--axi-border-panel) solid var(--axi-ink-line);
137
+ border-radius: var(--axi-radius);
138
+ }
139
+ /* A rail that IS the edge of the page rather than a panel standing in the
140
+ layout - pinned to the viewport, or a drawer's contents. It keeps the one
141
+ border that faces the content and drops everything that says "this is an
142
+ object with space around it": no radius on the flush side, and no block,
143
+ because a block needs somewhere to fall.
144
+
145
+ The block is stated as what a free-standing rail has rather than as
146
+ something the flush one turns off, so there is exactly one box-shadow here
147
+ and it names the panel block. `box-shadow: none` would be a second spelling
148
+ of the raise for a theme to miss. */
149
+ .axi-rail:not(.axi-rail--flush) { box-shadow: var(--axi-shadow-panel); }
150
+ .axi-rail--flush {
151
+ border: 0;
152
+ border-right: var(--axi-border-panel) solid var(--axi-ink-line);
153
+ border-radius: 0;
154
+ }
155
+ .axi-rail__nav { display: flex; flex-direction: column; gap: 2px; }
156
+ .axi-rail__item {
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 10px;
160
+ width: 100%;
161
+ padding: 8px 10px;
162
+ font: 800 12.5px/1 var(--axi-sans);
163
+ color: var(--axi-text-dim);
164
+ text-align: left;
165
+ background: transparent;
166
+ border: var(--axi-border-control) solid transparent;
167
+ border-radius: var(--axi-radius-sm);
168
+ cursor: pointer;
169
+ }
170
+ .axi-rail__item:hover { color: var(--axi-text); background: var(--axi-surface-raised); }
171
+ /* The same treatment a pressed pill and a current tab get. Only ever one of
172
+ these on screen. Any aria-current value, because the right one depends on
173
+ what the rail navigates - `page` for a view, `location` for a place within
174
+ one - and the fill means the same thing either way. */
175
+ .axi-rail__item[aria-current],
176
+ .axi-rail__item[aria-current]:hover {
63
177
  background: var(--axi-accent);
64
178
  color: var(--axi-accent-ink);
65
179
  border-color: var(--axi-ink-line);
66
180
  box-shadow: var(--axi-shadow-control);
67
181
  }
182
+ /* An icon beside a label is a label for it, not a second accent - so it takes
183
+ the faint tone, and inside a filled item it follows the item's ink. */
184
+ .axi-rail__item .axi-icon { color: var(--axi-text-faint); }
185
+ .axi-rail__item[aria-current] .axi-icon { color: var(--axi-accent-ink); }
186
+ /* The second level, indented under the item it belongs to. */
187
+ .axi-rail__sub { display: flex; flex-direction: column; margin: 3px 0 5px 10px; }
188
+ .axi-rail__subitem {
189
+ padding: 5px 8px;
190
+ font: 700 11.5px/1.3 var(--axi-sans);
191
+ text-align: left;
192
+ color: var(--axi-text-faint);
193
+ background: transparent;
194
+ border: 0;
195
+ border-radius: var(--axi-radius-sm);
196
+ cursor: pointer;
197
+ }
198
+ .axi-rail__subitem:hover { color: var(--axi-text-dim); background: var(--axi-surface-raised); }
199
+ .axi-rail__subitem[aria-current] { color: var(--axi-text); }
200
+ /* An annotation on an item, not a measurement of it, so rule 5 draws it
201
+ outlined rather than filled. */
202
+ .axi-rail__mark {
203
+ margin-left: auto;
204
+ padding: 2px 5px;
205
+ font: var(--axi-t-micro);
206
+ letter-spacing: .04em;
207
+ text-transform: uppercase;
208
+ color: var(--axi-meta);
209
+ border: var(--axi-border-hairline) solid var(--axi-meta);
210
+ border-radius: var(--axi-radius-sm);
211
+ }
68
212
 
69
213
  /* ---------- notice ---------- */
70
214
  .axi-notice {
@@ -379,7 +523,8 @@
379
523
 
380
524
  @media (max-width: 640px) {
381
525
  .axi-mast__in { min-height: 0; padding: 10px 16px; gap: 12px; }
382
- .axi-tabs { margin-left: 0; width: 100%; overflow-x: auto; }
526
+ .axi-mast .axi-tabs { margin-left: 0; }
527
+ .axi-tabs { width: 100%; overflow-x: auto; }
383
528
  /* The offset blocks eat horizontal room a phone does not have, so the panel
384
529
  step drops to the control step here rather than to an invented number.
385
530
  The card's hover has to come down with it - left alone it kept deepening
package/src/tokens.css CHANGED
@@ -39,6 +39,28 @@
39
39
  --axi-ink-on-fill below, on the other side of the same token. */
40
40
  --axi-ground-deep: var(--axi-ink-line);
41
41
  --axi-rule: #3a4250; /* internal rules, inside an outlined panel */
42
+ /* --- the well --- */
43
+ /* A field sunk INTO a surface rather than raised off one: a table picker's
44
+ list, a bar's trough, a column of cards standing on the page. The language
45
+ had no name for it, and every consumer that needed one reached for a second
46
+ .axi-panel inside the first - two identical fills parted by a line, which
47
+ reads as one field with a stray rule through it.
48
+
49
+ Two tokens because a well is the exact inverse of a panel on both counts.
50
+ The fill is a SUBTRACTION from whatever is behind it, not a colour of its
51
+ own: spelled as --axi-ground it is right in this theme, where surfaces are
52
+ opaque and the page really is what shows through a hole in one, and wrong
53
+ in a glass theme, where an opaque page colour inside a translucent pane is
54
+ a black patch rather than a recess. A theme restates it as a darkening at
55
+ alpha and the panel's own tint carries through.
56
+
57
+ The line is --axi-rule and not --axi-ink-line, because ink only reads
58
+ against a fill lighter than itself. Every outline in this language is
59
+ near-black on the assumption of a ground the lines can be darker than; a
60
+ well IS at ground level, so an ink edge round one is a black line on a
61
+ black field. Raised takes ink, sunk takes the rule. */
62
+ --axi-well-fill: var(--axi-ground);
63
+ --axi-well-line: var(--axi-rule);
42
64
  --axi-text: #f4f6f9;
43
65
  --axi-text-dim: #a7b0be;
44
66
  --axi-text-faint: #7c8695;