@floegence/floe-webapp-core 0.86.60 → 0.86.63

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 (122) hide show
  1. package/dist/app/FloeRegistryContributions.js +0 -3
  2. package/dist/components/chat/blocks/BlockRenderer.js +1 -1
  3. package/dist/components/chat/blocks/CodeDiffBlock.js +56 -56
  4. package/dist/components/chat/blocks/ImageBlock.js +1 -1
  5. package/dist/components/chat/blocks/ToolCallBlock.js +57 -57
  6. package/dist/components/chat/status/ConnectionStatus.js +10 -10
  7. package/dist/components/deck/DeckCell.js +25 -25
  8. package/dist/components/deck/DeckContextMenu.js +20 -20
  9. package/dist/components/deck/DeckGrid.js +1 -1
  10. package/dist/components/deck/DeckTopBar.js +42 -42
  11. package/dist/components/deck/DropZonePreview.js +6 -6
  12. package/dist/components/deck/LayoutSelector.js +53 -53
  13. package/dist/components/deck/WidgetFrame.js +28 -28
  14. package/dist/components/deck/WidgetPalette.js +31 -31
  15. package/dist/components/deck/WidgetTypeSwitcher.js +31 -31
  16. package/dist/components/file-browser/Breadcrumb.js +27 -27
  17. package/dist/components/file-browser/DirectoryTree.js +15 -15
  18. package/dist/components/file-browser/DragPreview.js +58 -58
  19. package/dist/components/file-browser/FileBrowser.js +17 -17
  20. package/dist/components/file-browser/FileBrowserStatusBar.js +18 -18
  21. package/dist/components/file-browser/FileBrowserToolbar.js +27 -27
  22. package/dist/components/file-browser/FileContextMenu.js +47 -47
  23. package/dist/components/file-browser/FileGridView.js +53 -51
  24. package/dist/components/file-browser/FileItemDecorations.js +30 -27
  25. package/dist/components/file-browser/FileListView.js +114 -114
  26. package/dist/components/icons/ActivityRailIcons.d.ts +30 -0
  27. package/dist/components/icons/ActivityRailIcons.js +68 -0
  28. package/dist/components/icons/index.d.ts +1 -0
  29. package/dist/components/icons/index.js +1357 -1357
  30. package/dist/components/launchpad/Launchpad.js +14 -14
  31. package/dist/components/launchpad/LaunchpadGrid.js +7 -7
  32. package/dist/components/launchpad/LaunchpadItem.js +11 -11
  33. package/dist/components/launchpad/LaunchpadPagination.js +16 -16
  34. package/dist/components/launchpad/LaunchpadSearch.js +9 -9
  35. package/dist/components/layout/ActivityBar.js +61 -63
  36. package/dist/components/layout/DisplayModeSwitcher.d.ts +10 -0
  37. package/dist/components/layout/DisplayModeSwitcher.js +102 -33
  38. package/dist/components/layout/MobileNavigationPanel.js +36 -36
  39. package/dist/components/layout/MobileTabBar.js +39 -39
  40. package/dist/components/layout/Panel.js +16 -16
  41. package/dist/components/layout/ResizeHandle.js +19 -19
  42. package/dist/components/layout/Shell.d.ts +0 -6
  43. package/dist/components/layout/Shell.js +208 -254
  44. package/dist/components/layout/Sidebar.js +37 -36
  45. package/dist/components/layout/SidebarPane.js +19 -19
  46. package/dist/components/layout/ThemeToggle.d.ts +19 -0
  47. package/dist/components/layout/ThemeToggle.js +39 -0
  48. package/dist/components/layout/TopBar.js +16 -16
  49. package/dist/components/layout/TopBarIconButton.js +15 -15
  50. package/dist/components/layout/index.d.ts +1 -1
  51. package/dist/components/loading/LoadingOverlay.js +9 -9
  52. package/dist/components/loading/Skeleton.js +11 -11
  53. package/dist/components/ui/Button.js +44 -36
  54. package/dist/components/ui/Card.d.ts +11 -62
  55. package/dist/components/ui/Card.js +64 -237
  56. package/dist/components/ui/Checkbox.js +79 -79
  57. package/dist/components/ui/CommandPalette.js +56 -55
  58. package/dist/components/ui/Dialog.js +91 -93
  59. package/dist/components/ui/DirectoryInput.js +22 -22
  60. package/dist/components/ui/DirectoryPicker.js +8 -8
  61. package/dist/components/ui/Dropdown.js +89 -87
  62. package/dist/components/ui/FeedbackIndicator.js +57 -57
  63. package/dist/components/ui/FileOpenPicker.js +37 -37
  64. package/dist/components/ui/FileSavePicker.js +41 -41
  65. package/dist/components/ui/FloatingWindow.js +213 -205
  66. package/dist/components/ui/Form.js +76 -76
  67. package/dist/components/ui/HighlightBlock.js +14 -14
  68. package/dist/components/ui/Input.js +106 -109
  69. package/dist/components/ui/Pagination.js +86 -86
  70. package/dist/components/ui/Popover.d.ts +33 -0
  71. package/dist/components/ui/Popover.js +133 -0
  72. package/dist/components/ui/ProcessingIndicator.js +344 -345
  73. package/dist/components/ui/Progress.js +94 -97
  74. package/dist/components/ui/QuoteBlock.js +25 -25
  75. package/dist/components/ui/Radio.js +91 -91
  76. package/dist/components/ui/SegmentedControl.js +24 -24
  77. package/dist/components/ui/Stepper.js +68 -60
  78. package/dist/components/ui/Switch.js +25 -25
  79. package/dist/components/ui/Tabs.js +188 -163
  80. package/dist/components/ui/Tag.js +12 -9
  81. package/dist/components/ui/Tooltip.js +30 -30
  82. package/dist/components/ui/floatingPresence.d.ts +7 -0
  83. package/dist/components/ui/floatingPresence.js +80 -39
  84. package/dist/components/ui/index.d.ts +2 -1
  85. package/dist/components/ui/picker/DirectoryFavorites.js +27 -27
  86. package/dist/components/ui/picker/PickerBase.js +107 -107
  87. package/dist/context/ComponentRegistry.d.ts +0 -8
  88. package/dist/context/ComponentRegistry.js +61 -67
  89. package/dist/context/FloeConfigContext.d.ts +0 -6
  90. package/dist/context/FloeConfigContext.js +9 -15
  91. package/dist/context/NotificationContext.js +4 -4
  92. package/dist/context/ThemeContext.js +71 -70
  93. package/dist/context/index.d.ts +1 -1
  94. package/dist/display-mode.css +2 -45
  95. package/dist/floe.css +205 -43
  96. package/dist/full.js +701 -695
  97. package/dist/icons.js +112 -102
  98. package/dist/language.css +320 -0
  99. package/dist/layout.js +33 -35
  100. package/dist/primitives.css +23 -17
  101. package/dist/standalone.css +1 -0
  102. package/dist/styles/themes/classicSemanticTokens.d.ts +122 -122
  103. package/dist/styles/themes/classicSemanticTokens.js +140 -138
  104. package/dist/styles/themes/index.d.ts +3 -1
  105. package/dist/styles/themes/index.js +10 -10
  106. package/dist/styles/themes/presets.d.ts +2 -2
  107. package/dist/styles/themes/presets.js +34 -34
  108. package/dist/styles/tokens.d.ts +152 -68
  109. package/dist/styles/tokens.js +87 -49
  110. package/dist/styles.css +1 -1
  111. package/dist/surface.css +214 -117
  112. package/dist/themes/dark.css +64 -64
  113. package/dist/themes/light.css +64 -64
  114. package/dist/ui.css +171 -255
  115. package/dist/ui.js +176 -178
  116. package/dist/utils/hotInteraction.d.ts +7 -0
  117. package/dist/utils/hotInteraction.js +12 -12
  118. package/dist/widgets/TerminalWidget.js +28 -28
  119. package/dist/workbench-composition.css +15 -15
  120. package/package.json +1 -1
  121. package/dist/components/layout/BottomBar.d.ts +0 -29
  122. package/dist/components/layout/BottomBar.js +0 -70
package/dist/surface.css CHANGED
@@ -12,18 +12,22 @@
12
12
  * propagate that private value through its editor/content descendants. */
13
13
  /* Preblend quiet seams onto the carrying background. Opaque borders avoid an
14
14
  * extra blending path when native editors repaint their text and caret. */
15
+ /* The language owns the seams. Surfaces no longer invent their own edge
16
+ * percentages or shadows: a seam is the language's hairline preblended onto the
17
+ * carrying canvas (opaque, so editors repaint text and caret without a second
18
+ * blend pass), and elevation comes from the language's three-step ladder. */
15
19
  :root {
16
- --floe-surface-edge: color-mix(in srgb, var(--foreground) 11.5%, var(--background));
17
- --floe-surface-divider: color-mix(in srgb, var(--foreground) 8.5%, var(--background));
18
- --floe-surface-highlight: rgb(255 255 255 / 50%);
19
- --floe-surface-shade: rgb(0 0 0 / 12%);
20
+ --floe-surface-edge: color-mix(in srgb, var(--foreground) 9%, var(--background));
21
+ --floe-surface-divider: color-mix(in srgb, var(--foreground) 5.5%, var(--background));
22
+ --floe-surface-highlight: transparent;
23
+ --floe-surface-shade: color-mix(in srgb, #000 8%, transparent);
20
24
  }
21
25
 
22
26
  /* Keep dimensions and semantic palette tokens intact. Neutral boundaries become
23
27
  * quiet seams; material and existing fills carry the grouping. High contrast
24
28
  * retains the original boundaries. Explicit rich Card variants stay independent. */
25
29
  @media (forced-colors: none) {
26
- :where(:root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light']))
30
+ :where(:root:not([data-floe-shell-theme='hc-light']))
27
31
  :is(
28
32
  .border-border,
29
33
  [class~='border-border/40'],
@@ -37,13 +41,24 @@
37
41
  .border-input,
38
42
  [data-floe-input-surface],
39
43
  [data-floe-input-surface] *,
44
+ /* A form field draws its own boundary. The seam is a hairline preblended
45
+ * onto the page ground, which is right for a divider sitting on that
46
+ * ground and wrong for a well carved into a card: 9% over the page
47
+ * against a 4.5% fill lands within one step of the fill, so the edge
48
+ * vanishes instead of quietening. Fields carry `--floe-field-edge`,
49
+ * measured against the plane they actually sit on. */
50
+ input,
51
+ textarea,
52
+ select,
53
+ .floe-field,
54
+ [data-floe-control],
40
55
  [data-floe-surface-part='tab'],
41
56
  [data-floe-card-variant]:not([data-floe-card-variant='default'])
42
57
  )
43
58
  ) {
44
59
  border-color: var(--floe-surface-edge);
45
60
  }
46
- :where(:root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light']))
61
+ :where(:root:not([data-floe-shell-theme='hc-light']))
47
62
  :is(
48
63
  [data-floe-shell-slot],
49
64
  .workbench-widget__header,
@@ -58,7 +73,7 @@
58
73
  --terminal-panel-border: var(--floe-surface-divider);
59
74
  border-color: var(--floe-surface-divider);
60
75
  }
61
- :where(:root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light']))
76
+ :where(:root:not([data-floe-shell-theme='hc-light']))
62
77
  :is(
63
78
  .bg-border,
64
79
  .workbench-dock__divider,
@@ -69,69 +84,64 @@
69
84
  ) {
70
85
  background-color: var(--floe-surface-divider);
71
86
  }
72
- :where(:root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light']))
87
+ :where(:root:not([data-floe-shell-theme='hc-light']))
73
88
  .workbench-canvas__grid {
74
89
  opacity: 0.35;
75
90
  }
76
- :root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light'])
91
+ :root:not([data-floe-shell-theme='hc-light'])
77
92
  [data-floe-surface-divider] {
78
93
  border-color: var(--floe-surface-divider);
79
94
  }
80
- :root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light'])
95
+ :root:not([data-floe-shell-theme='hc-light'])
81
96
  .workbench-surface {
82
97
  --wb-dock-material-border: var(--floe-surface-edge);
83
98
  --wb-dock-material-border-top: var(--floe-surface-edge);
84
99
  --wb-dock-material-shadow: var(
85
100
  --floe-surface-shadow-raised,
86
- 0 2px 5px -2px var(--floe-surface-shade),
87
- inset 0 1px 1px var(--floe-surface-highlight)
101
+ 0 2px 5px -2px var(--floe-surface-shade)
88
102
  );
89
103
  }
90
104
  }
91
105
  :root.dark {
92
106
  --floe-surface-highlight: transparent;
93
- --floe-surface-shadow-raised: 0 0 transparent;
107
+ --floe-surface-shadow-raised: var(--floe-elevation-raised);
94
108
  --floe-surface-shadow-inset: 0 0 transparent;
95
- --floe-surface-shade: rgb(0 0 0 / 20%);
109
+ --floe-surface-shade: color-mix(in srgb, #000 34%, transparent);
96
110
  }
97
111
 
98
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface] {
112
+ [data-floe-surface] {
99
113
  --floe-surface-decoration: 0 0 transparent;
100
114
  box-shadow: var(--floe-surface-decoration);
101
115
  }
102
116
 
103
117
  /* Material never owns movement. Keep the motion component's property list,
104
118
  * duration and easing together instead of remapping its transition shorthand. */
105
- [data-floe-surface-style='soft-neumorphic']
106
- [data-floe-surface]:not(
119
+ [data-floe-surface]:not(
107
120
  :where(
108
121
  .floe-floating-presence,
109
122
  [data-floe-surface-part='switch-thumb'],
110
123
  [data-floe-surface-part='radio-dot'],
111
- [data-floe-surface-part='progress-fill']
124
+ [data-floe-surface-part='progress-fill'],
125
+ /* The travelling pill's transition is a motion timeline, not a palette
126
+ * fade. This rule sits after the segment-pill rule and shares its
127
+ * specificity, so without this exemption it would win and freeze the
128
+ * pill's transform. */
129
+ [data-floe-surface-part='segment-pill']
112
130
  )
113
131
  ) {
114
132
  transition-property: color, background-color, border-color, opacity;
115
133
  }
116
134
 
117
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface='raised'] {
118
- --floe-surface-decoration: var(
119
- --floe-surface-shadow-raised,
120
- 0 1px 3px -2px var(--floe-surface-shade),
121
- inset 0 1px 1px var(--floe-surface-highlight)
122
- );
135
+ [data-floe-surface='raised'] {
136
+ --floe-surface-decoration: var(--floe-surface-shadow-raised, var(--floe-elevation-raised));
123
137
  }
124
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface='inset'] {
125
- --floe-surface-decoration: var(
126
- --floe-surface-shadow-inset,
127
- inset 0 1px 1px var(--floe-surface-shade),
128
- inset 0 -1px 1px var(--floe-surface-highlight)
129
- );
138
+ [data-floe-surface='inset'] {
139
+ --floe-surface-decoration: var(--floe-surface-shadow-inset, 0 0 transparent);
130
140
  }
131
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface='floating'] {
141
+ [data-floe-surface='floating'] {
132
142
  --floe-surface-decoration: var(
133
143
  --floe-surface-shadow-floating,
134
- var(--floe-window-shadow)
144
+ var(--floe-elevation-float)
135
145
  );
136
146
  background-color: var(--popover);
137
147
  -webkit-backdrop-filter: none;
@@ -140,25 +150,24 @@
140
150
 
141
151
  /* The geometry owner must not clip the moving panel's external shadow. Paint
142
152
  * containment belongs on the existing panel that also owns content clipping. */
143
- [data-floe-surface-style='soft-neumorphic'] [data-floe-geometry-surface='floating-window'] {
153
+ [data-floe-geometry-surface='floating-window'] {
144
154
  contain: layout style;
145
155
  }
146
- [data-floe-surface-style='soft-neumorphic'] [data-floe-floating-window-surface='true'] {
156
+ [data-floe-floating-window-surface='true'] {
147
157
  contain: paint;
148
158
  }
149
- [data-floe-surface-style='soft-neumorphic'] [data-floe-dialog-backdrop]:not(.floe-bottom-drawer-backdrop) {
159
+ [data-floe-dialog-backdrop]:not(.floe-bottom-drawer-backdrop) {
150
160
  background-color: rgb(0 0 0 / 16%);
151
161
  -webkit-backdrop-filter: none;
152
162
  backdrop-filter: none;
153
163
  }
154
- :root.dark[data-floe-surface-style='soft-neumorphic'] [data-floe-dialog-backdrop]:not(.floe-bottom-drawer-backdrop) {
164
+ :root.dark[data-floe-dialog-backdrop]:not(.floe-bottom-drawer-backdrop) {
155
165
  background-color: rgb(0 0 0 / 36%);
156
166
  }
157
167
 
158
168
  /* A new selection is visible in its first painted frame. Fading the two faces
159
169
  * leaves the old choice visually active after the native state has changed. */
160
- [data-floe-surface-style='soft-neumorphic']
161
- :is(
170
+ :is(
162
171
  [data-floe-surface-part='choice'],
163
172
  [data-floe-surface-part='indicator'],
164
173
  [data-floe-surface-part='tab'],
@@ -167,20 +176,57 @@
167
176
  transition: none;
168
177
  }
169
178
 
179
+ /* A tab strip that continues past its box fades toward that edge. The fade is
180
+ * part of the rail, not a decoration on the tabs, so it follows the scroll
181
+ * state instead of any one item's position. */
182
+ [data-floe-surface-part='tab-rail'] {
183
+ --floe-rail-fade: 20px;
184
+ mask-image: none;
185
+ }
186
+ [data-floe-surface-part='tab-rail'][data-floe-overflow-start][data-floe-overflow-end] {
187
+ mask-image: linear-gradient(
188
+ to right,
189
+ transparent 0,
190
+ #000 var(--floe-rail-fade),
191
+ #000 calc(100% - var(--floe-rail-fade)),
192
+ transparent 100%
193
+ );
194
+ }
195
+ [data-floe-surface-part='tab-rail'][data-floe-overflow-start]:not([data-floe-overflow-end]) {
196
+ mask-image: linear-gradient(to right, transparent 0, #000 var(--floe-rail-fade));
197
+ }
198
+ [data-floe-surface-part='tab-rail'][data-floe-overflow-end]:not([data-floe-overflow-start]) {
199
+ mask-image: linear-gradient(to right, #000 calc(100% - var(--floe-rail-fade)), transparent 100%);
200
+ }
201
+
202
+ /* A travelling pill is the selection, not a second copy of it: the faces under
203
+ * it stay flat so the only thing that moves is the material. It keeps the
204
+ * language's one motion family, and a reduced-motion request lands it in place
205
+ * without the sweep. */
206
+ [data-floe-surface-part='segment-pill'] {
207
+ transition:
208
+ transform var(--floe-duration-base) var(--floe-ease-out),
209
+ width var(--floe-duration-base) var(--floe-ease-out);
210
+ will-change: transform, width;
211
+ }
212
+ @media (prefers-reduced-motion: reduce) {
213
+ [data-floe-surface-part='segment-pill'] {
214
+ transition: none;
215
+ }
216
+ }
217
+
170
218
  /* A compound input has one decorative boundary, including host compositions
171
219
  * that contain an Input component. Dense nested actions stay flat. */
172
- [data-floe-surface-style='soft-neumorphic'] [data-floe-input-surface] [data-floe-surface],
173
- [data-floe-surface-style='soft-neumorphic']
174
- [data-floe-surface='raised']
220
+ [data-floe-input-surface] [data-floe-surface],
221
+ [data-floe-surface='raised']
175
222
  [data-floe-surface='raised']:not([data-floe-surface-part]),
176
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface='flat'] {
223
+ [data-floe-surface='flat'] {
177
224
  --floe-surface-decoration: 0 0 transparent;
178
225
  }
179
226
 
180
227
  /* Decoration cannot replace independent keyboard indicators. Inputs, including
181
228
  * button-based compound fields, continue to use input-focus.css exclusively. */
182
- [data-floe-surface-style='soft-neumorphic']
183
- [data-floe-surface]:focus-visible:not(
229
+ [data-floe-surface]:focus-visible:not(
184
230
  :where(input, textarea, select, [data-floe-input-surface], [data-floe-input-surface] *)
185
231
  ) {
186
232
  box-shadow:
@@ -189,24 +235,23 @@
189
235
  var(--floe-surface-decoration);
190
236
  }
191
237
 
192
- [data-floe-surface-style='soft-neumorphic'] .workbench-widget[data-floe-surface] {
238
+ .workbench-widget[data-floe-surface] {
193
239
  /* Some local palettes use translucent body tints. Composite onto an opaque
194
240
  * card base instead of reintroducing glass or letting content bleed through. */
195
241
  background: linear-gradient(var(--wb-widget-body), var(--wb-widget-body)), var(--card);
196
242
  }
197
- [data-floe-surface-style='soft-neumorphic'] .workbench-widget.is-selected {
243
+ .workbench-widget.is-selected {
198
244
  border-color: transparent;
199
245
  }
200
- [data-floe-surface-style='soft-neumorphic']
201
- .workbench-widget.is-selected
246
+ .workbench-widget.is-selected
202
247
  .workbench-widget__header {
203
248
  background: color-mix(in srgb, var(--wb-accent) 8%, var(--card));
204
249
  color: var(--wb-accent);
205
250
  }
206
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface-interacting='true'] {
251
+ [data-floe-surface-interacting='true'] {
207
252
  --floe-surface-decoration: var(
208
253
  --floe-surface-shadow-interacting,
209
- 0 1px 2px var(--floe-surface-shade)
254
+ var(--floe-elevation-raised)
210
255
  );
211
256
  transition: none;
212
257
  }
@@ -215,13 +260,11 @@
215
260
  * Tight static highlights carry material without large soft shadows per item.
216
261
  * Functional transforms, progress widths, and SVG strokes retain their motion. */
217
262
  @media (forced-colors: none) {
218
- :root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light']) {
219
- --floe-surface-compact-raised:
220
- 0 1px 2px var(--floe-surface-shade), inset 0 1px 1px var(--floe-surface-highlight);
221
- --floe-surface-compact-inset:
222
- inset 0 1px 1px var(--floe-surface-shade), inset 0 -1px 1px var(--floe-surface-highlight);
263
+ :root:not([data-floe-shell-theme='hc-light']) {
264
+ --floe-surface-compact-raised: var(--floe-elevation-raised);
265
+ --floe-surface-compact-inset: 0 0 transparent;
223
266
  }
224
- :where(:root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light'])) {
267
+ :where(:root:not([data-floe-shell-theme='hc-light'])) {
225
268
  & [data-floe-surface-part][data-floe-surface='raised'] {
226
269
  --floe-surface-decoration: var(
227
270
  --floe-surface-shadow-raised,
@@ -234,12 +277,28 @@
234
277
  var(--floe-surface-compact-inset)
235
278
  );
236
279
  }
280
+ /* Two tags, two gestures. An outline rides along with content; a wash
281
+ * wins the eye inside a list. The accent is the tone's own color, so one
282
+ * recipe covers every semantic variant in every preset. */
283
+ &
284
+ [data-floe-surface-part='badge'][data-floe-tone='soft']:not(
285
+ [data-floe-surface='flat']
286
+ ) {
287
+ background-color: transparent;
288
+ border-color: var(--floe-line-strong);
289
+ }
290
+ &
291
+ [data-floe-surface-part='badge'][data-floe-tone='solid']:not(
292
+ [data-floe-surface='flat']
293
+ ) {
294
+ background-color: color-mix(in srgb, var(--tag-accent) 15%, transparent);
295
+ border-color: color-mix(in srgb, var(--tag-accent) 26%, transparent);
296
+ }
237
297
  & [data-floe-surface-part='badge'] {
238
298
  /* These can repeat in dense lists. Keep lighting inside the painted box. */
239
299
  --floe-surface-decoration: var(
240
300
  --floe-surface-shadow-raised,
241
- inset 0 1px 1px var(--floe-surface-highlight),
242
- inset 0 -1px 1px var(--floe-surface-shade)
301
+ 0 0 0 1px color-mix(in srgb, var(--foreground) 6%, transparent)
243
302
  );
244
303
  }
245
304
  & [data-floe-surface-part='badge'][data-floe-surface='inset'],
@@ -259,10 +318,16 @@
259
318
  --floe-surface-decoration: var(--floe-surface-shadow-raised, none);
260
319
  }
261
320
  & :is([data-floe-surface-part='rail'], [data-floe-surface-part='tab-rail']) {
262
- background-color: color-mix(in srgb, var(--card) 65%, var(--muted));
321
+ background-color: var(--floe-well);
263
322
  }
264
323
  /* Keep the existing box metrics, but let the carrying fill reach the edge.
265
324
  * An opaque seam based on the page background becomes a dark frame here. */
325
+ /* A card is a filled plane on the canvas. Its separation is the fill step
326
+ * plus the canvas gutter, so it carries no contour and no resting shadow. */
327
+ & [data-floe-card-variant='default'][data-floe-surface] {
328
+ border-color: transparent;
329
+ --floe-surface-decoration: 0 0 transparent;
330
+ }
266
331
  &
267
332
  :is(
268
333
  [data-floe-card-variant='default'],
@@ -282,26 +347,55 @@
282
347
  [data-floe-surface-part='choice']:not([data-floe-selected='true']):hover:not(
283
348
  :has(:disabled)
284
349
  ) {
285
- background-color: color-mix(in srgb, var(--foreground) 4%, var(--card));
350
+ background-color: var(--floe-state-hover);
286
351
  }
287
352
  & [data-floe-surface-part='choice'][data-floe-selected='true'] {
288
- background-color: var(--accent);
289
- }
290
- /* Compact choices need a clear selected face, independent of material.
291
- * Large choice cards retain their native dot/checkmark and quiet tint. */
353
+ background-color: var(--floe-state-selected);
354
+ }
355
+ /* A committed compact choice is a filled control, because the fill is the
356
+ * state indicator and has to carry 3:1 against its rail by itself. A
357
+ * translucent wash with a 34% accent hairline measures about 1.1:1, which
358
+ * reads as the same surface turned slightly warmer, not as a selection.
359
+ * The semantic pair is the one opaque thing on the rail; relief would only
360
+ * restate the fill, so it stays off. */
292
361
  & [data-floe-choice-variant='button'][data-floe-selected='true'],
293
362
  & [data-floe-surface-part='segment'][aria-checked='true'] {
294
363
  background-color: var(--primary);
295
364
  color: var(--primary-foreground);
365
+ border-color: var(--primary);
296
366
  --floe-surface-decoration: 0 0 transparent;
297
367
  }
368
+ /* A selected tab is a surface sitting proud of its rail: solid fill, the
369
+ * language's hairline and the first elevation step. This is the one place a
370
+ * small control is allowed relief, because the rail sits directly behind it. */
371
+ & [data-floe-surface-part='tab']:not([aria-selected='true']):hover:not(:has(:disabled)) {
372
+ background-color: var(--floe-state-hover);
373
+ color: var(--foreground);
374
+ }
298
375
  & [data-floe-surface-part='tab'][aria-selected='true'] {
299
- background-color: var(--accent);
376
+ background-color: var(--card);
300
377
  color: var(--foreground);
378
+ border-color: var(--floe-line);
379
+ --floe-surface-decoration: var(--floe-elevation-raised);
380
+ }
381
+ /* The travelling pill is a selected tab at rest: same fill, same hairline,
382
+ * same first elevation step. It carries them by itself because it has no
383
+ * per-option state to inherit from. */
384
+ & [data-floe-surface-part='segment-pill'] {
385
+ background-color: var(--card);
386
+ border: 1px solid var(--floe-line);
387
+ --floe-surface-decoration: var(--floe-elevation-raised);
301
388
  }
302
389
  & [data-floe-surface-part='segment'][aria-checked='false'] {
303
390
  background-color: transparent;
304
391
  }
392
+ &
393
+ [data-floe-surface-part='segment'][aria-checked='false']:hover:not(
394
+ :disabled
395
+ ):not(:has(:disabled)) {
396
+ background-color: var(--floe-state-hover);
397
+ color: var(--foreground);
398
+ }
305
399
  & [data-floe-surface-part='tab'].border-primary {
306
400
  border-color: color-mix(in srgb, var(--primary) 24%, var(--card));
307
401
  }
@@ -314,8 +408,8 @@
314
408
  background-color: var(--card);
315
409
  }
316
410
  & [data-floe-surface-part='step'][data-floe-step-state='current'] {
317
- background-color: color-mix(in srgb, var(--primary) 16%, var(--card));
318
- color: var(--primary);
411
+ background-color: var(--floe-state-selected-strong);
412
+ color: var(--foreground);
319
413
  }
320
414
  & [data-floe-surface-part='indicator']:not([data-floe-selected='true']),
321
415
  & [data-floe-surface-part='switch-track']:not([data-floe-selected='true']) {
@@ -332,13 +426,8 @@
332
426
  [data-floe-surface-part='indicator'],
333
427
  [data-floe-surface-part='switch-track']
334
428
  )[data-floe-surface='inset'] {
335
- /* At 14–24 px, a one-pixel light edge and tight well stay legible at 1x.
336
- * Broad opposing blurs make both the contour and the glyph look unfocused. */
337
- --floe-surface-decoration: var(
338
- --floe-surface-shadow-inset,
339
- inset 0 1px 1px var(--floe-surface-shade),
340
- inset 0 -1px 0 var(--floe-surface-highlight)
341
- );
429
+ /* Small indicators rely on their own fill contrast, not on relief. */
430
+ --floe-surface-decoration: var(--floe-surface-shadow-inset, 0 0 transparent);
342
431
  }
343
432
  & [data-floe-surface-part='switch-track'] {
344
433
  transition-duration: 140ms;
@@ -348,7 +437,7 @@
348
437
  /* A hollow hairline keeps an unchecked radio quieter than its selected dot.
349
438
  * Border-box dimensions and hit targets stay fixed; there is no blurred well. */
350
439
  border-width: 1px;
351
- border-color: color-mix(in srgb, var(--muted-foreground) 88%, var(--card));
440
+ border-color: color-mix(in srgb, var(--muted-foreground) 72%, var(--card));
352
441
  background-color: transparent;
353
442
  --floe-surface-decoration: var(--floe-surface-shadow-inset, none);
354
443
  }
@@ -356,7 +445,7 @@
356
445
  [data-floe-surface-part='indicator'][data-floe-selected='true']:has(
357
446
  [data-floe-surface-part='radio-dot']
358
447
  ) {
359
- background-color: color-mix(in srgb, var(--primary) 6%, var(--card));
448
+ background-color: var(--floe-state-selected);
360
449
  }
361
450
  & [data-floe-surface-part='radio-dot'] {
362
451
  background-color: var(--foreground);
@@ -366,10 +455,8 @@
366
455
  transition-property: width, background-color;
367
456
  }
368
457
  & [data-floe-surface-part='action']:active:not(:disabled) {
369
- --floe-surface-decoration: var(
370
- --floe-surface-shadow-inset,
371
- var(--floe-surface-compact-inset)
372
- );
458
+ /* Press settles the control instead of embossing it. */
459
+ --floe-surface-decoration: 0 0 transparent;
373
460
  }
374
461
  /* Hidden native inputs delegate their indicator to the visible existing
375
462
  * face. A pointer selection does not receive this keyboard-only outline. */
@@ -382,34 +469,34 @@
382
469
  box-shadow: var(--floe-surface-decoration);
383
470
  }
384
471
  }
385
- :where(:root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light'])) {
472
+ :where(:root:not([data-floe-shell-theme='hc-light'])) {
386
473
  & :is([data-floe-input-surface], input, textarea, select)[data-floe-surface='inset'] {
387
- background-color: color-mix(in srgb, var(--card) 50%, var(--background));
474
+ background-color: var(--floe-well);
388
475
  }
389
476
  }
390
477
  }
391
478
 
392
- /* Text-labelled actions, rails and fields use one contour. Stacked inset and
393
- * contact shadows turn a quiet toolbar into an embossed frame in light mode.
479
+ /* Text-labelled actions and fields use one contour. Stacked inset and contact
480
+ * shadows turn a quiet toolbar into an embossed frame.
481
+ * A rail and a tab rail are wells, not planes: they recede behind their
482
+ * segments, and the relief belongs to the one selected item inside them. Giving
483
+ * the well the raised step made it cast a drop shadow in dark palettes while
484
+ * staying flat in light ones, so the same control had two different shapes.
394
485
  * Small check/switch indicators retain their independent relief and contrast. */
395
- [data-floe-surface-style='soft-neumorphic']
396
- :is(
486
+ :is(
397
487
  [data-floe-surface-part='action'],
398
- [data-floe-surface-part='rail'],
399
- [data-floe-surface-part='tab-rail'],
400
488
  [data-floe-surface-part='tab'],
401
489
  [data-floe-surface-part='segment']
402
490
  )[data-floe-surface] {
403
491
  --floe-surface-decoration: var(--floe-surface-shadow-raised, 0 0 transparent);
404
492
  }
405
- [data-floe-surface-style='soft-neumorphic']
406
- :is([data-floe-input-surface], input, textarea, select)[data-floe-surface='inset'] {
493
+ :is([data-floe-input-surface], input, textarea, select)[data-floe-surface='inset'] {
407
494
  --floe-surface-decoration: var(--floe-surface-shadow-inset, 0 0 transparent);
408
495
  }
409
496
 
410
497
  /* Explicit host opt-outs and compound input boundaries outrank compact states. */
411
- :root[data-floe-surface-style='soft-neumorphic'] [data-floe-surface='flat'],
412
- :root[data-floe-surface-style='soft-neumorphic'] [data-floe-input-surface] [data-floe-surface] {
498
+ :root[data-floe-surface='flat'],
499
+ :root[data-floe-input-surface] [data-floe-surface] {
413
500
  --floe-surface-decoration: 0 0 transparent;
414
501
  }
415
502
 
@@ -417,7 +504,7 @@
417
504
  * distinct from ordinary Cards, Dialogs and menus. Only this visible panel owns
418
505
  * material; the outer geometry owner and its input lifecycle stay unchanged. */
419
506
  @media (forced-colors: none) {
420
- :root[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light'])
507
+ :root:not([data-floe-shell-theme='hc-light'])
421
508
  [data-floe-floating-window-surface='true'][data-floe-surface='floating'] {
422
509
  --floe-window-radius: var(--floe-radius-floating);
423
510
  background: var(--floe-window-background);
@@ -462,36 +549,45 @@
462
549
  }
463
550
  }
464
551
 
465
- /* Dark shell palettes use a restrained dark step for active chrome so the
466
- * window remains discoverable without a bright outline or animated glow. */
467
- :root.dark[data-floe-surface-style='soft-neumorphic']:not([data-floe-shell-theme='hc-light'])
552
+ /* Dark shell palettes use a restrained step for active chrome so the window
553
+ * remains discoverable without a bright outline or animated glow. The step is
554
+ * taken toward the foreground, not toward the page ground: an accent wash alone
555
+ * lands within three 8-bit units of a near-black window body (soft-black
556
+ * measured 19,20,21 on 16,17,19), so the titlebar stopped being identifiable in
557
+ * grayscale at all. Foreground is light in every dark preset, so mixing 8% of it
558
+ * guarantees an upward step from any accent while title text keeps 5.3:1 or
559
+ * better. Light presets take no such step: their background is already far from
560
+ * the accent wash, and darkening it there would eat title text contrast. */
561
+ :root.dark:not([data-floe-shell-theme='hc-light'])
468
562
  [data-floe-floating-window-surface='true'][data-floe-floating-window-state='active']
469
563
  > [data-floe-floating-window-titlebar='true'] {
470
- background-color: color-mix(in srgb, var(--background) 38%, var(--floe-window-titlebar-background));
564
+ background-color: color-mix(
565
+ in srgb,
566
+ var(--accent) 24%,
567
+ color-mix(in srgb, var(--foreground) 8%, var(--floe-window-titlebar-background))
568
+ );
471
569
  }
472
570
 
473
571
  /* FloatingWindow keeps rich text at scale 1 throughout its short presence
474
- * motion. Only presence promotes this panel; the existing geometry owner
475
- * already promotes direct drag/resize, so adding a second hot layer is wasteful. */
476
- :root[data-floe-surface-style='soft-neumorphic'] .floe-floating-window-motion {
572
+ * motion: a window is a large reading surface, so a scale that suits a menu
573
+ * would blur its text mid-flight. The class lives on the panel, not on <html>,
574
+ * so the selector has to match the panel or the override never applies.
575
+ *
576
+ * Promotion belongs to the two transitioning states only; a settled window must
577
+ * not hold a compositor layer (see .floe-floating-presence in floe.css). */
578
+ .floe-floating-window-motion {
477
579
  --floe-floating-enter-scale: 1;
478
580
  --floe-floating-exit-scale: 1;
479
581
  --floe-floating-enter-y: 3px;
480
582
  --floe-floating-exit-y: 2px;
481
- will-change: auto;
482
- }
483
- :root[data-floe-surface-style='soft-neumorphic'] .floe-floating-window-motion:is(
484
- [data-floating-presence='entering'], [data-floating-presence='exiting']
485
- ) {
486
- will-change: opacity, transform;
487
583
  }
488
584
 
489
585
  /* High contrast stays flat, with real boundaries and keyboard indicators. */
490
- :root[data-floe-surface-style='soft-neumorphic'][data-floe-shell-theme='hc-light']
586
+ :root[data-floe-shell-theme='hc-light']
491
587
  [data-floe-surface] {
492
588
  --floe-surface-decoration: 0 0 transparent;
493
589
  }
494
- :root[data-floe-surface-style='soft-neumorphic'][data-floe-shell-theme='hc-light']
590
+ :root[data-floe-shell-theme='hc-light']
495
591
  .workbench-widget.is-selected {
496
592
  border-color: var(--wb-accent);
497
593
  }
@@ -505,25 +601,28 @@
505
601
  box-shadow: none;
506
602
  transition: none;
507
603
  }
508
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface] {
604
+ [data-floe-surface] {
509
605
  --floe-surface-decoration: 0 0 transparent;
510
606
  }
511
- [data-floe-surface-style='soft-neumorphic']
512
607
  [data-floe-surface]:focus-visible:not(
513
608
  :where(input, textarea, select, [data-floe-input-surface], [data-floe-input-surface] *)
514
609
  ) {
515
610
  outline: 2px solid Highlight;
516
611
  outline-offset: 2px;
517
612
  }
518
- [data-floe-surface-style='soft-neumorphic'] .workbench-widget.is-selected {
613
+ .workbench-widget.is-selected {
519
614
  border-color: Highlight;
520
615
  }
521
616
  }
522
617
  @media (prefers-reduced-motion: reduce) {
523
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface] {
618
+ /* `:root` carries the specificity this reset needs: a surface part may pair
619
+ * two attribute selectors to own a transition of its own, and a bare
620
+ * `[data-floe-surface]` would lose to it. Reduced motion is a user setting,
621
+ * so it must win without `!important`. */
622
+ :root [data-floe-surface] {
524
623
  transition: none;
525
624
  }
526
- [data-floe-surface-style='soft-neumorphic'] [data-floe-surface-part='indicator'] > svg {
625
+ :root [data-floe-surface-part='indicator'] > svg {
527
626
  animation: none;
528
627
  }
529
628
  }
@@ -539,18 +638,16 @@
539
638
  }
540
639
  }
541
640
 
542
- :root[data-floe-surface-style='soft-neumorphic'][data-floe-shell-theme='hc-light']
641
+ :root[data-floe-shell-theme='hc-light']
543
642
  [data-floe-surface-part='choice']:has([data-floe-choice-input]:focus-visible),
544
- :root[data-floe-surface-style='soft-neumorphic'][data-floe-shell-theme='hc-light']
643
+ :root[data-floe-shell-theme='hc-light']
545
644
  [data-floe-choice-input]:focus-visible
546
645
  + :is([data-floe-surface-part='indicator'], [data-floe-surface-part='switch-track']) {
547
646
  outline: 2px solid var(--ring);
548
647
  outline-offset: 3px;
549
648
  }
550
649
  @media (forced-colors: active) {
551
- [data-floe-surface-style='soft-neumorphic']
552
650
  [data-floe-surface-part='choice']:has([data-floe-choice-input]:focus-visible),
553
- [data-floe-surface-style='soft-neumorphic']
554
651
  [data-floe-choice-input]:focus-visible
555
652
  + :is([data-floe-surface-part='indicator'], [data-floe-surface-part='switch-track']) {
556
653
  outline: 2px solid Highlight;