@xenosystem/elements-react 0.0.1 → 0.0.3

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.
Files changed (49) hide show
  1. package/LICENSE +18 -18
  2. package/README.md +41 -41
  3. package/package.json +58 -58
  4. package/src/chrome-separated.css +132 -132
  5. package/src/chrome-unified.css +72 -72
  6. package/src/containers/Badges.css +116 -116
  7. package/src/containers/avatar.css +70 -70
  8. package/src/containers/card.css +75 -75
  9. package/src/containers/chip.css +103 -103
  10. package/src/containers/list-row.css +132 -132
  11. package/src/containers/message-bubble.css +112 -112
  12. package/src/containers/panel.css +120 -120
  13. package/src/containers/table.css +102 -102
  14. package/src/containers/tile.css +17 -17
  15. package/src/content/callout.css +77 -77
  16. package/src/content/caret.css +30 -30
  17. package/src/content/code-block.css +159 -159
  18. package/src/content/collapsible.css +80 -80
  19. package/src/content/inline-code.css +14 -14
  20. package/src/content/model-picker.css +219 -219
  21. package/src/content/source-card.css +99 -99
  22. package/src/content/sources-disclosure.css +118 -118
  23. package/src/controls.css +453 -453
  24. package/src/fonts.css +34 -34
  25. package/src/forms/Checkbox.css +114 -114
  26. package/src/forms/RadioGroup.css +115 -115
  27. package/src/goo.css +97 -97
  28. package/src/icon-motion.css +1909 -1909
  29. package/src/layout/ResizablePanel.css +79 -79
  30. package/src/nav/sidebar.css +296 -296
  31. package/src/overlays/date-time-picker.css +238 -238
  32. package/src/overlays/menu.css +195 -195
  33. package/src/overlays/modal.css +167 -167
  34. package/src/overlays/picker-field.css +81 -81
  35. package/src/overlays/pill-filter.css +129 -129
  36. package/src/overlays/reveal.css +56 -56
  37. package/src/overlays/segmented-control.css +185 -185
  38. package/src/overlays/tabs.css +143 -143
  39. package/src/overlays/tooltip.css +126 -126
  40. package/src/scrollbar.css +184 -184
  41. package/src/size.css +179 -179
  42. package/src/status/progress-bar.css +88 -88
  43. package/src/status/spinner.css +33 -33
  44. package/src/status/statusindicator.css +65 -65
  45. package/src/status/steptimeline.css +137 -137
  46. package/src/status/thinkingcube.css +75 -75
  47. package/src/xeno-element.css +60 -60
  48. package/src/xeno-elements.css +71 -71
  49. package/src/xeno-theme.css +99 -99
@@ -1,143 +1,143 @@
1
- /*
2
- * <Tabs> — a horizontal tablist over a hairline baseline, plus an optional panel. CSS-first: the
3
- * selected tab carries `data-selection="on"`, a disabled tab carries `data-availability="disabled"`.
4
- * Size metrics arrive as inline `--xeno-h/-padx/-gap/-font` vars from the size tokens. Monochrome only.
5
- */
6
-
7
- .xeno-tabs {
8
- display: flex;
9
- flex-direction: column;
10
- gap: 16px;
11
- }
12
-
13
- .xeno-tablist {
14
- position: relative; /* the containing block the indicator's measured offset is relative to */
15
- display: inline-flex;
16
- align-items: stretch;
17
- gap: 2px;
18
- border-bottom: 1px solid var(--xeno-border);
19
- }
20
-
21
- .xeno-tab {
22
- position: relative;
23
- display: inline-flex;
24
- align-items: center;
25
- justify-content: center;
26
- gap: var(--xeno-gap, 6px);
27
- height: var(--xeno-h, 32px);
28
- padding: 0 var(--xeno-padx, 12px);
29
- font-family: inherit;
30
- font-size: var(--xeno-font, 14px);
31
- font-weight: 500;
32
- line-height: 1;
33
- white-space: nowrap;
34
- color: var(--xeno-muted);
35
- background: transparent;
36
- border: none;
37
- cursor: pointer;
38
- user-select: none;
39
- transition: color var(--xeno-dur) var(--xeno-ease);
40
- }
41
-
42
- /* The underline — ONE hairline bar for the whole tablist, which SLIDES from the tab you left to the tab
43
- you chose. It used to be a `::after` on each tab scaling from 0 to 1, so it appeared on the new tab
44
- and vanished from the old one: two events, no journey, and no clue where it came from.
45
- Its position and width arrive as `--xeno-tab-x` / `--xeno-tab-w`, measured by the component (a tab is
46
- as wide as its label — layout is the only thing that can answer that). Everything after those two
47
- numbers is CSS: the travel, the stretch, and the settle. */
48
- .xeno-tab-indicator {
49
- /* Mid-flight the bar ELONGATES along its travel and thins — reaching for where it is going — then
50
- rebounds a hair as it lands. Both scale with how far it has to go (`--xeno-tab-dist`). */
51
- --xeno-tab-stretch: 0.22;
52
- --xeno-tab-thin: 0.2;
53
- --xeno-tab-goo-x: calc(1 + var(--xeno-tab-stretch) * var(--xeno-tab-dist, 1));
54
- --xeno-tab-goo-y: calc(1 - var(--xeno-tab-thin) * var(--xeno-tab-dist, 1));
55
- --xeno-tab-goo-x2: calc(1 - var(--xeno-tab-thin) * 0.25);
56
- --xeno-tab-goo-y2: calc(1 + var(--xeno-tab-thin) * 0.25);
57
- /* ONE duration for the travel and the deformation, or the stretch peaks off-centre and reads as a
58
- glitch rather than as weight. */
59
- --xeno-tab-dur: 300ms;
60
- --xeno-tab-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
61
- position: absolute;
62
- left: 0;
63
- bottom: -1px;
64
- width: var(--xeno-tab-w, 0px);
65
- height: 2px;
66
- translate: var(--xeno-tab-x, 0px) 0;
67
- background: var(--xeno-text);
68
- /* Square. At 2px tall even `--xeno-radius-hair` is 75% of the height — semicircular ends, i.e. a
69
- capsule. Below about 4px there is no radius that reads as a corner, so a hairline stays a hairline. */
70
- border-radius: 0;
71
- pointer-events: none;
72
- }
73
- /* Not armed until the selection has actually changed once — otherwise the first measurement after mount
74
- would slide the bar in from the left edge on page load. */
75
- .xeno-tablist[data-motion='ready'] .xeno-tab-indicator {
76
- transition:
77
- translate var(--xeno-tab-dur) var(--xeno-tab-ease),
78
- width var(--xeno-tab-dur) var(--xeno-tab-ease);
79
- }
80
-
81
- .xeno-tab:hover:not([data-availability='disabled']) {
82
- color: var(--xeno-text);
83
- }
84
-
85
- .xeno-tab[data-selection='on'] {
86
- color: var(--xeno-text);
87
- }
88
-
89
- /* The stretch, keyed off the tablist's `data-index`. One keyframe track per position, all identical:
90
- moving to another tab only changes which rule matches, and a browser will not restart an animation
91
- whose name did not change. Eight positions; past that the bar still travels, it just stops
92
- stretching. */
93
- .xeno-tablist[data-motion='ready'][data-index='0'] .xeno-tab-indicator { animation: xeno-tab-goo-0 var(--xeno-tab-dur) 1; }
94
- .xeno-tablist[data-motion='ready'][data-index='1'] .xeno-tab-indicator { animation: xeno-tab-goo-1 var(--xeno-tab-dur) 1; }
95
- .xeno-tablist[data-motion='ready'][data-index='2'] .xeno-tab-indicator { animation: xeno-tab-goo-2 var(--xeno-tab-dur) 1; }
96
- .xeno-tablist[data-motion='ready'][data-index='3'] .xeno-tab-indicator { animation: xeno-tab-goo-3 var(--xeno-tab-dur) 1; }
97
- .xeno-tablist[data-motion='ready'][data-index='4'] .xeno-tab-indicator { animation: xeno-tab-goo-4 var(--xeno-tab-dur) 1; }
98
- .xeno-tablist[data-motion='ready'][data-index='5'] .xeno-tab-indicator { animation: xeno-tab-goo-5 var(--xeno-tab-dur) 1; }
99
- .xeno-tablist[data-motion='ready'][data-index='6'] .xeno-tab-indicator { animation: xeno-tab-goo-6 var(--xeno-tab-dur) 1; }
100
- .xeno-tablist[data-motion='ready'][data-index='7'] .xeno-tab-indicator { animation: xeno-tab-goo-7 var(--xeno-tab-dur) 1; }
101
- @keyframes xeno-tab-goo-0 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
102
- @keyframes xeno-tab-goo-1 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
103
- @keyframes xeno-tab-goo-2 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
104
- @keyframes xeno-tab-goo-3 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
105
- @keyframes xeno-tab-goo-4 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
106
- @keyframes xeno-tab-goo-5 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
107
- @keyframes xeno-tab-goo-6 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
108
- @keyframes xeno-tab-goo-7 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
109
-
110
- .xeno-tab:focus-visible {
111
- outline: none;
112
- border-radius: var(--xeno-radius-xs);
113
- box-shadow:
114
- 0 0 0 2px var(--xeno-canvas),
115
- 0 0 0 4px var(--xeno-border);
116
- }
117
-
118
- .xeno-tab[data-availability='disabled'] {
119
- opacity: 0.4;
120
- cursor: not-allowed;
121
- }
122
-
123
- .xeno-tab-panel {
124
- color: var(--xeno-text);
125
- outline: none;
126
- }
127
- .xeno-tab-panel:focus-visible {
128
- border-radius: var(--xeno-radius-sm);
129
- box-shadow:
130
- 0 0 0 2px var(--xeno-canvas),
131
- 0 0 0 4px var(--xeno-border);
132
- }
133
-
134
- @media (prefers-reduced-motion: reduce) {
135
- .xeno-tab,
136
- .xeno-tablist[data-motion='ready'] .xeno-tab-indicator {
137
- transition: none;
138
- }
139
- /* The bar still marks the tab — it just gets there without travelling or stretching. */
140
- .xeno-tab-indicator {
141
- animation: none !important;
142
- }
143
- }
1
+ /*
2
+ * <Tabs> — a horizontal tablist over a hairline baseline, plus an optional panel. CSS-first: the
3
+ * selected tab carries `data-selection="on"`, a disabled tab carries `data-availability="disabled"`.
4
+ * Size metrics arrive as inline `--xeno-h/-padx/-gap/-font` vars from the size tokens. Monochrome only.
5
+ */
6
+
7
+ .xeno-tabs {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: 16px;
11
+ }
12
+
13
+ .xeno-tablist {
14
+ position: relative; /* the containing block the indicator's measured offset is relative to */
15
+ display: inline-flex;
16
+ align-items: stretch;
17
+ gap: 2px;
18
+ border-bottom: 1px solid var(--xeno-border);
19
+ }
20
+
21
+ .xeno-tab {
22
+ position: relative;
23
+ display: inline-flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ gap: var(--xeno-gap, 6px);
27
+ height: var(--xeno-h, 32px);
28
+ padding: 0 var(--xeno-padx, 12px);
29
+ font-family: inherit;
30
+ font-size: var(--xeno-font, 14px);
31
+ font-weight: 500;
32
+ line-height: 1;
33
+ white-space: nowrap;
34
+ color: var(--xeno-muted);
35
+ background: transparent;
36
+ border: none;
37
+ cursor: pointer;
38
+ user-select: none;
39
+ transition: color var(--xeno-dur) var(--xeno-ease);
40
+ }
41
+
42
+ /* The underline — ONE hairline bar for the whole tablist, which SLIDES from the tab you left to the tab
43
+ you chose. It used to be a `::after` on each tab scaling from 0 to 1, so it appeared on the new tab
44
+ and vanished from the old one: two events, no journey, and no clue where it came from.
45
+ Its position and width arrive as `--xeno-tab-x` / `--xeno-tab-w`, measured by the component (a tab is
46
+ as wide as its label — layout is the only thing that can answer that). Everything after those two
47
+ numbers is CSS: the travel, the stretch, and the settle. */
48
+ .xeno-tab-indicator {
49
+ /* Mid-flight the bar ELONGATES along its travel and thins — reaching for where it is going — then
50
+ rebounds a hair as it lands. Both scale with how far it has to go (`--xeno-tab-dist`). */
51
+ --xeno-tab-stretch: 0.22;
52
+ --xeno-tab-thin: 0.2;
53
+ --xeno-tab-goo-x: calc(1 + var(--xeno-tab-stretch) * var(--xeno-tab-dist, 1));
54
+ --xeno-tab-goo-y: calc(1 - var(--xeno-tab-thin) * var(--xeno-tab-dist, 1));
55
+ --xeno-tab-goo-x2: calc(1 - var(--xeno-tab-thin) * 0.25);
56
+ --xeno-tab-goo-y2: calc(1 + var(--xeno-tab-thin) * 0.25);
57
+ /* ONE duration for the travel and the deformation, or the stretch peaks off-centre and reads as a
58
+ glitch rather than as weight. */
59
+ --xeno-tab-dur: 300ms;
60
+ --xeno-tab-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
61
+ position: absolute;
62
+ left: 0;
63
+ bottom: -1px;
64
+ width: var(--xeno-tab-w, 0px);
65
+ height: 2px;
66
+ translate: var(--xeno-tab-x, 0px) 0;
67
+ background: var(--xeno-text);
68
+ /* Square. At 2px tall even `--xeno-radius-hair` is 75% of the height — semicircular ends, i.e. a
69
+ capsule. Below about 4px there is no radius that reads as a corner, so a hairline stays a hairline. */
70
+ border-radius: 0;
71
+ pointer-events: none;
72
+ }
73
+ /* Not armed until the selection has actually changed once — otherwise the first measurement after mount
74
+ would slide the bar in from the left edge on page load. */
75
+ .xeno-tablist[data-motion='ready'] .xeno-tab-indicator {
76
+ transition:
77
+ translate var(--xeno-tab-dur) var(--xeno-tab-ease),
78
+ width var(--xeno-tab-dur) var(--xeno-tab-ease);
79
+ }
80
+
81
+ .xeno-tab:hover:not([data-availability='disabled']) {
82
+ color: var(--xeno-text);
83
+ }
84
+
85
+ .xeno-tab[data-selection='on'] {
86
+ color: var(--xeno-text);
87
+ }
88
+
89
+ /* The stretch, keyed off the tablist's `data-index`. One keyframe track per position, all identical:
90
+ moving to another tab only changes which rule matches, and a browser will not restart an animation
91
+ whose name did not change. Eight positions; past that the bar still travels, it just stops
92
+ stretching. */
93
+ .xeno-tablist[data-motion='ready'][data-index='0'] .xeno-tab-indicator { animation: xeno-tab-goo-0 var(--xeno-tab-dur) 1; }
94
+ .xeno-tablist[data-motion='ready'][data-index='1'] .xeno-tab-indicator { animation: xeno-tab-goo-1 var(--xeno-tab-dur) 1; }
95
+ .xeno-tablist[data-motion='ready'][data-index='2'] .xeno-tab-indicator { animation: xeno-tab-goo-2 var(--xeno-tab-dur) 1; }
96
+ .xeno-tablist[data-motion='ready'][data-index='3'] .xeno-tab-indicator { animation: xeno-tab-goo-3 var(--xeno-tab-dur) 1; }
97
+ .xeno-tablist[data-motion='ready'][data-index='4'] .xeno-tab-indicator { animation: xeno-tab-goo-4 var(--xeno-tab-dur) 1; }
98
+ .xeno-tablist[data-motion='ready'][data-index='5'] .xeno-tab-indicator { animation: xeno-tab-goo-5 var(--xeno-tab-dur) 1; }
99
+ .xeno-tablist[data-motion='ready'][data-index='6'] .xeno-tab-indicator { animation: xeno-tab-goo-6 var(--xeno-tab-dur) 1; }
100
+ .xeno-tablist[data-motion='ready'][data-index='7'] .xeno-tab-indicator { animation: xeno-tab-goo-7 var(--xeno-tab-dur) 1; }
101
+ @keyframes xeno-tab-goo-0 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
102
+ @keyframes xeno-tab-goo-1 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
103
+ @keyframes xeno-tab-goo-2 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
104
+ @keyframes xeno-tab-goo-3 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
105
+ @keyframes xeno-tab-goo-4 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
106
+ @keyframes xeno-tab-goo-5 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
107
+ @keyframes xeno-tab-goo-6 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
108
+ @keyframes xeno-tab-goo-7 { 0% { scale: 1 1; } 34% { scale: var(--xeno-tab-goo-x) var(--xeno-tab-goo-y); } 72% { scale: var(--xeno-tab-goo-x2) var(--xeno-tab-goo-y2); } 100% { scale: 1 1; } }
109
+
110
+ .xeno-tab:focus-visible {
111
+ outline: none;
112
+ border-radius: var(--xeno-radius-xs);
113
+ box-shadow:
114
+ 0 0 0 2px var(--xeno-canvas),
115
+ 0 0 0 4px var(--xeno-border);
116
+ }
117
+
118
+ .xeno-tab[data-availability='disabled'] {
119
+ opacity: 0.4;
120
+ cursor: not-allowed;
121
+ }
122
+
123
+ .xeno-tab-panel {
124
+ color: var(--xeno-text);
125
+ outline: none;
126
+ }
127
+ .xeno-tab-panel:focus-visible {
128
+ border-radius: var(--xeno-radius-sm);
129
+ box-shadow:
130
+ 0 0 0 2px var(--xeno-canvas),
131
+ 0 0 0 4px var(--xeno-border);
132
+ }
133
+
134
+ @media (prefers-reduced-motion: reduce) {
135
+ .xeno-tab,
136
+ .xeno-tablist[data-motion='ready'] .xeno-tab-indicator {
137
+ transition: none;
138
+ }
139
+ /* The bar still marks the tab — it just gets there without travelling or stretching. */
140
+ .xeno-tab-indicator {
141
+ animation: none !important;
142
+ }
143
+ }
@@ -1,126 +1,126 @@
1
- /*
2
- * Tooltip — a hover/focus hint. CSS-first reveal driven by `data-state` (JS-controlled), positioned on
3
- * a `data-side` of the trigger. Monochrome shell: an elevated panel with a hairline border and muted
4
- * 12px mono text; rounded squares only. Motion is transform/opacity, behind reduced-motion.
5
- */
6
-
7
- .xeno-tooltip {
8
- position: relative;
9
- display: inline-flex;
10
- }
11
-
12
- .xeno-tooltip-panel {
13
- position: absolute;
14
- z-index: 50;
15
- width: max-content;
16
- max-width: 240px;
17
- padding: 6px 8px;
18
- background: var(--xeno-elevated);
19
- border: 1px solid var(--xeno-border);
20
- border-radius: var(--xeno-radius-sm);
21
- color: var(--xeno-muted);
22
- font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
23
- font-size: 12px;
24
- line-height: 1.4;
25
- letter-spacing: 0.01em;
26
- text-align: left;
27
- white-space: normal;
28
- pointer-events: none;
29
- opacity: 0;
30
- visibility: hidden;
31
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
32
- transition:
33
- opacity var(--xeno-dur) var(--xeno-ease),
34
- transform var(--xeno-dur) var(--xeno-ease),
35
- visibility 0s linear var(--xeno-dur);
36
- }
37
-
38
- /* Sides — centre with inset + auto margins (works even when the panel is wider than the trigger) so
39
- * the entrance transform stays free to animate. A small gap separates the panel from the trigger. */
40
- .xeno-tooltip-panel[data-side='top'] {
41
- bottom: 100%;
42
- left: 0;
43
- right: 0;
44
- margin-inline: auto;
45
- margin-bottom: 8px;
46
- transform: translateY(4px);
47
- }
48
- .xeno-tooltip-panel[data-side='bottom'] {
49
- top: 100%;
50
- left: 0;
51
- right: 0;
52
- margin-inline: auto;
53
- margin-top: 8px;
54
- transform: translateY(-4px);
55
- }
56
- .xeno-tooltip-panel[data-side='left'] {
57
- right: 100%;
58
- top: 0;
59
- bottom: 0;
60
- margin-block: auto;
61
- margin-right: 8px;
62
- transform: translateX(4px);
63
- }
64
- .xeno-tooltip-panel[data-side='right'] {
65
- left: 100%;
66
- top: 0;
67
- bottom: 0;
68
- margin-block: auto;
69
- margin-left: 8px;
70
- transform: translateX(-4px);
71
- }
72
-
73
- /* Revealed — settle to the resting position; keep visibility on until the fade completes on close. */
74
- .xeno-tooltip-panel[data-state='open'] {
75
- opacity: 1;
76
- visibility: visible;
77
- transform: translate(0, 0);
78
- transition:
79
- opacity var(--xeno-dur) var(--xeno-ease),
80
- transform var(--xeno-dur) var(--xeno-ease),
81
- visibility 0s linear 0s;
82
- }
83
-
84
- /* Arrow — a small rounded square rotated 45°, overlapping the panel edge so it reads as a pointer. */
85
- .xeno-tooltip-arrow {
86
- position: absolute;
87
- width: 7px;
88
- height: 7px;
89
- background: var(--xeno-elevated);
90
- border-radius: var(--xeno-radius-hair);
91
- }
92
- .xeno-tooltip-panel[data-side='top'] .xeno-tooltip-arrow {
93
- left: 50%;
94
- bottom: -3px;
95
- transform: translateX(-50%) rotate(45deg);
96
- }
97
- .xeno-tooltip-panel[data-side='bottom'] .xeno-tooltip-arrow {
98
- left: 50%;
99
- top: -3px;
100
- transform: translateX(-50%) rotate(45deg);
101
- }
102
- .xeno-tooltip-panel[data-side='left'] .xeno-tooltip-arrow {
103
- top: 50%;
104
- right: -3px;
105
- transform: translateY(-50%) rotate(45deg);
106
- }
107
- .xeno-tooltip-panel[data-side='right'] .xeno-tooltip-arrow {
108
- top: 50%;
109
- left: -3px;
110
- transform: translateY(-50%) rotate(45deg);
111
- }
112
-
113
- @media (prefers-reduced-motion: reduce) {
114
- .xeno-tooltip-panel {
115
- transform: none;
116
- transition:
117
- opacity var(--xeno-dur) linear,
118
- visibility 0s linear var(--xeno-dur);
119
- }
120
- .xeno-tooltip-panel[data-state='open'] {
121
- transform: none;
122
- transition:
123
- opacity var(--xeno-dur) linear,
124
- visibility 0s linear 0s;
125
- }
126
- }
1
+ /*
2
+ * Tooltip — a hover/focus hint. CSS-first reveal driven by `data-state` (JS-controlled), positioned on
3
+ * a `data-side` of the trigger. Monochrome shell: an elevated panel with a hairline border and muted
4
+ * 12px mono text; rounded squares only. Motion is transform/opacity, behind reduced-motion.
5
+ */
6
+
7
+ .xeno-tooltip {
8
+ position: relative;
9
+ display: inline-flex;
10
+ }
11
+
12
+ .xeno-tooltip-panel {
13
+ position: absolute;
14
+ z-index: 50;
15
+ width: max-content;
16
+ max-width: 240px;
17
+ padding: 6px 8px;
18
+ background: var(--xeno-elevated);
19
+ border: 1px solid var(--xeno-border);
20
+ border-radius: var(--xeno-radius-sm);
21
+ color: var(--xeno-muted);
22
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
23
+ font-size: 12px;
24
+ line-height: 1.4;
25
+ letter-spacing: 0.01em;
26
+ text-align: left;
27
+ white-space: normal;
28
+ pointer-events: none;
29
+ opacity: 0;
30
+ visibility: hidden;
31
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.24);
32
+ transition:
33
+ opacity var(--xeno-dur) var(--xeno-ease),
34
+ transform var(--xeno-dur) var(--xeno-ease),
35
+ visibility 0s linear var(--xeno-dur);
36
+ }
37
+
38
+ /* Sides — centre with inset + auto margins (works even when the panel is wider than the trigger) so
39
+ * the entrance transform stays free to animate. A small gap separates the panel from the trigger. */
40
+ .xeno-tooltip-panel[data-side='top'] {
41
+ bottom: 100%;
42
+ left: 0;
43
+ right: 0;
44
+ margin-inline: auto;
45
+ margin-bottom: 8px;
46
+ transform: translateY(4px);
47
+ }
48
+ .xeno-tooltip-panel[data-side='bottom'] {
49
+ top: 100%;
50
+ left: 0;
51
+ right: 0;
52
+ margin-inline: auto;
53
+ margin-top: 8px;
54
+ transform: translateY(-4px);
55
+ }
56
+ .xeno-tooltip-panel[data-side='left'] {
57
+ right: 100%;
58
+ top: 0;
59
+ bottom: 0;
60
+ margin-block: auto;
61
+ margin-right: 8px;
62
+ transform: translateX(4px);
63
+ }
64
+ .xeno-tooltip-panel[data-side='right'] {
65
+ left: 100%;
66
+ top: 0;
67
+ bottom: 0;
68
+ margin-block: auto;
69
+ margin-left: 8px;
70
+ transform: translateX(-4px);
71
+ }
72
+
73
+ /* Revealed — settle to the resting position; keep visibility on until the fade completes on close. */
74
+ .xeno-tooltip-panel[data-state='open'] {
75
+ opacity: 1;
76
+ visibility: visible;
77
+ transform: translate(0, 0);
78
+ transition:
79
+ opacity var(--xeno-dur) var(--xeno-ease),
80
+ transform var(--xeno-dur) var(--xeno-ease),
81
+ visibility 0s linear 0s;
82
+ }
83
+
84
+ /* Arrow — a small rounded square rotated 45°, overlapping the panel edge so it reads as a pointer. */
85
+ .xeno-tooltip-arrow {
86
+ position: absolute;
87
+ width: 7px;
88
+ height: 7px;
89
+ background: var(--xeno-elevated);
90
+ border-radius: var(--xeno-radius-hair);
91
+ }
92
+ .xeno-tooltip-panel[data-side='top'] .xeno-tooltip-arrow {
93
+ left: 50%;
94
+ bottom: -3px;
95
+ transform: translateX(-50%) rotate(45deg);
96
+ }
97
+ .xeno-tooltip-panel[data-side='bottom'] .xeno-tooltip-arrow {
98
+ left: 50%;
99
+ top: -3px;
100
+ transform: translateX(-50%) rotate(45deg);
101
+ }
102
+ .xeno-tooltip-panel[data-side='left'] .xeno-tooltip-arrow {
103
+ top: 50%;
104
+ right: -3px;
105
+ transform: translateY(-50%) rotate(45deg);
106
+ }
107
+ .xeno-tooltip-panel[data-side='right'] .xeno-tooltip-arrow {
108
+ top: 50%;
109
+ left: -3px;
110
+ transform: translateY(-50%) rotate(45deg);
111
+ }
112
+
113
+ @media (prefers-reduced-motion: reduce) {
114
+ .xeno-tooltip-panel {
115
+ transform: none;
116
+ transition:
117
+ opacity var(--xeno-dur) linear,
118
+ visibility 0s linear var(--xeno-dur);
119
+ }
120
+ .xeno-tooltip-panel[data-state='open'] {
121
+ transform: none;
122
+ transition:
123
+ opacity var(--xeno-dur) linear,
124
+ visibility 0s linear 0s;
125
+ }
126
+ }