@motion-proto/live-tokens 0.44.0 → 0.46.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist-plugin/index.cjs +1 -1
  3. package/dist-plugin/index.js +1 -1
  4. package/package.json +1 -1
  5. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +0 -1
  6. package/src/editor/component-editor/scaffolding/ComponentFileMenu.svelte +1 -1
  7. package/src/editor/core/palettes/colorHarmony.ts +35 -6
  8. package/src/editor/core/palettes/paletteDerivation.ts +2 -3
  9. package/src/editor/core/store/editorTypes.ts +2 -2
  10. package/src/editor/docs/CodeBlock.svelte +0 -2
  11. package/src/editor/docs/Docs.svelte +0 -3
  12. package/src/editor/pages/ComponentEditorPage.svelte +3 -5
  13. package/src/editor/pages/EditorShell.svelte +3 -5
  14. package/src/editor/ui/ColorEditPanel.svelte +19 -22
  15. package/src/editor/ui/EditorViewSwitcher.svelte +2 -3
  16. package/src/editor/ui/FileLoadList.svelte +7 -9
  17. package/src/editor/ui/ManifestFileManager.svelte +1 -3
  18. package/src/editor/ui/ProjectFontsSection.svelte +0 -4
  19. package/src/editor/ui/SurfacesTab.svelte +3 -3
  20. package/src/editor/ui/TextStylesSection.svelte +6 -8
  21. package/src/editor/ui/ThemeFileManager.svelte +1 -3
  22. package/src/editor/ui/UIEasingSelector.svelte +0 -2
  23. package/src/editor/ui/UIFontFamilySelector.svelte +9 -3
  24. package/src/editor/ui/UIMenuButton.svelte +230 -0
  25. package/src/editor/ui/UIPaletteSelector.svelte +0 -1
  26. package/src/editor/ui/VariablesTab.svelte +3 -4
  27. package/src/editor/ui/colors/AxisNumeral.svelte +70 -0
  28. package/src/editor/ui/colors/ColorReadouts.svelte +0 -2
  29. package/src/editor/ui/colors/ColorStory.svelte +24 -77
  30. package/src/editor/ui/colors/ColorWheel.svelte +48 -29
  31. package/src/editor/ui/colors/ColorsTab.svelte +361 -212
  32. package/src/editor/ui/colors/HarmonyAxesList.svelte +264 -141
  33. package/src/editor/ui/colors/LightnessBar.svelte +0 -2
  34. package/src/editor/ui/colors/harmonyDrag.ts +10 -0
  35. package/src/editor/ui/colors/harmonyModeIcons.ts +5 -0
  36. package/src/editor/ui/colors/paletteBaseColor.ts +25 -39
  37. package/src/live-tokens/data/tokens.generated.css +28 -28
  38. package/src/system/components/ImageLightbox.svelte +0 -2
  39. package/src/system/components/SectionDivider.svelte +4 -4
  40. package/src/editor/core/palettes/recommendText.ts +0 -216
  41. package/src/editor/core/palettes/solveTextContrast.ts +0 -254
@@ -234,7 +234,5 @@
234
234
  font-weight: var(--ui-font-weight-semibold);
235
235
  color: var(--ui-text-tertiary);
236
236
  font-family: var(--ui-font-mono);
237
- text-transform: uppercase;
238
- letter-spacing: 0.04em;
239
237
  }
240
238
  </style>
@@ -5,7 +5,7 @@
5
5
  import { resolveAliasChain } from '../core/palettes/tokenRegistry';
6
6
  import { editorState } from '../core/store/editorStore';
7
7
  import { CSS_VAR_CHANGE_EVENT } from '../core/cssVarSync';
8
- import type { FontFamily, FontSource } from '../core/themes/themeTypes';
8
+ import type { FontFamily, FontSource, FontSourceKind } from '../core/themes/themeTypes';
9
9
  import UITokenSelector from './UITokenSelector.svelte';
10
10
  import UIOptionList from './UIOptionList.svelte';
11
11
  import UIOptionItem from './UIOptionItem.svelte';
@@ -28,6 +28,13 @@
28
28
  onchange,
29
29
  }: Props = $props();
30
30
 
31
+ const SOURCE_KIND_LABELS: Record<FontSourceKind, string> = {
32
+ google: 'Google Fonts',
33
+ typekit: 'Typekit',
34
+ 'css-url': 'CSS URL',
35
+ 'font-face': 'Font face',
36
+ };
37
+
31
38
  const options = [
32
39
  { key: 'display', label: 'Display' },
33
40
  { key: 'sans', label: 'Sans' },
@@ -219,7 +226,7 @@
219
226
  <div class="pfs-submenu">
220
227
  {#each fontSources as source (source.id)}
221
228
  {#if source.families.length > 0}
222
- <div class="pfs-source-label">{source.label ?? source.kind}</div>
229
+ <div class="pfs-source-label">{source.label ?? SOURCE_KIND_LABELS[source.kind]}</div>
223
230
  {#each source.families as fam (fam.id)}
224
231
  <UIOptionItem
225
232
  active={chosenFamilyId === fam.id}
@@ -329,6 +336,5 @@
329
336
  font-family: var(--ui-font-mono);
330
337
  font-size: var(--ui-font-size-xs);
331
338
  color: var(--ui-text-tertiary);
332
- text-transform: uppercase;
333
339
  }
334
340
  </style>
@@ -0,0 +1,230 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from 'svelte';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ interface Props {
6
+ /** Heading line inside the menu; also its accessible name. */
7
+ header: string;
8
+ /** Accessible name for an icon-only trigger; a text trigger names itself. */
9
+ triggerLabel?: string;
10
+ /** Lets the caller style the trigger as its own furniture via `:global`. */
11
+ triggerClass?: string;
12
+ menuMinWidth?: string;
13
+ trigger: Snippet;
14
+ children: Snippet<[{ close: () => void }]>;
15
+ }
16
+
17
+ let {
18
+ header,
19
+ triggerLabel,
20
+ triggerClass = '',
21
+ menuMinWidth = '14rem',
22
+ trigger,
23
+ children,
24
+ }: Props = $props();
25
+
26
+ let open = $state(false);
27
+ let btnEl = $state<HTMLButtonElement | undefined>(undefined);
28
+ let menuEl = $state<HTMLDivElement | undefined>(undefined);
29
+ let initialItem = $state<'first' | 'last'>('first');
30
+
31
+ function items(): HTMLButtonElement[] {
32
+ return menuEl ? Array.from(menuEl.querySelectorAll<HTMLButtonElement>('button:not(:disabled)')) : [];
33
+ }
34
+
35
+ function close(refocusTrigger = true) {
36
+ if (!open) return;
37
+ open = false;
38
+ if (refocusTrigger) btnEl?.focus();
39
+ }
40
+
41
+ function toggle() {
42
+ if (open) {
43
+ close();
44
+ } else {
45
+ initialItem = 'first';
46
+ open = true;
47
+ }
48
+ }
49
+
50
+ function onTriggerKeydown(e: KeyboardEvent) {
51
+ if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;
52
+ e.preventDefault();
53
+ initialItem = e.key === 'ArrowUp' ? 'last' : 'first';
54
+ open = true;
55
+ }
56
+
57
+ // Enter and Space activate the focused item natively: items are real buttons.
58
+ function onMenuKeydown(e: KeyboardEvent) {
59
+ if (e.key === 'Escape' || e.key === 'Tab') {
60
+ e.preventDefault();
61
+ close();
62
+ return;
63
+ }
64
+ const list = items();
65
+ if (list.length === 0) return;
66
+ const idx = list.indexOf(document.activeElement as HTMLButtonElement);
67
+ let next = -1;
68
+ if (e.key === 'ArrowDown') next = idx === -1 ? 0 : (idx + 1) % list.length;
69
+ else if (e.key === 'ArrowUp') next = idx === -1 ? list.length - 1 : (idx - 1 + list.length) % list.length;
70
+ else if (e.key === 'Home') next = 0;
71
+ else if (e.key === 'End') next = list.length - 1;
72
+ if (next === -1) return;
73
+ e.preventDefault();
74
+ list[next].focus();
75
+ }
76
+
77
+ function onWindowKeydown(e: KeyboardEvent) {
78
+ if (e.key === 'Escape' && open) close();
79
+ }
80
+
81
+ function onDocumentMousedown(e: MouseEvent) {
82
+ if (!open) return;
83
+ const target = e.target as Node | null;
84
+ if (target && (btnEl?.contains(target) || menuEl?.contains(target))) return;
85
+ // The user is directing attention elsewhere; refocusing the trigger would
86
+ // steal focus from whatever they clicked.
87
+ close(false);
88
+ }
89
+
90
+ onMount(() => {
91
+ window.addEventListener('keydown', onWindowKeydown);
92
+ document.addEventListener('mousedown', onDocumentMousedown, true);
93
+ });
94
+
95
+ onDestroy(() => {
96
+ window.removeEventListener('keydown', onWindowKeydown);
97
+ document.removeEventListener('mousedown', onDocumentMousedown, true);
98
+ });
99
+
100
+ /* Fixed positioning escapes any parent overflow/stacking context. Anchored
101
+ below the trigger, right edges aligned; flips above near the viewport
102
+ bottom. Inline visibility rather than a state flag so the item focus that
103
+ follows lands on an already-visible menu (hidden elements refuse focus). */
104
+ function position(): void {
105
+ const btn = btnEl;
106
+ const menu = menuEl;
107
+ if (!btn || !menu) return;
108
+ const br = btn.getBoundingClientRect();
109
+ const mr = menu.getBoundingClientRect();
110
+ const margin = 8;
111
+ const vw = window.innerWidth;
112
+ const vh = window.innerHeight;
113
+ let left = br.right - mr.width;
114
+ if (left < margin) left = margin;
115
+ if (left + mr.width > vw - margin) left = vw - margin - mr.width;
116
+ let top = br.bottom + margin / 2;
117
+ if (top + mr.height > vh - margin) {
118
+ top = br.top - margin / 2 - mr.height;
119
+ if (top < margin) top = margin;
120
+ }
121
+ menu.style.left = `${left}px`;
122
+ menu.style.top = `${top}px`;
123
+ menu.style.visibility = 'visible';
124
+ }
125
+
126
+ $effect(() => {
127
+ if (!open) return;
128
+ let raf = requestAnimationFrame(() => {
129
+ raf = requestAnimationFrame(() => {
130
+ // The composed option primitives expose no role prop; stamping
131
+ // menuitem + tabindex here is what makes the popup a real menu with
132
+ // roving focus instead of a cluster of tab stops.
133
+ for (const b of items()) {
134
+ b.setAttribute('role', 'menuitem');
135
+ b.tabIndex = -1;
136
+ }
137
+ position();
138
+ const list = items();
139
+ (initialItem === 'last' ? list[list.length - 1] : list[0])?.focus();
140
+ });
141
+ });
142
+ window.addEventListener('scroll', position, true);
143
+ window.addEventListener('resize', position);
144
+ return () => {
145
+ cancelAnimationFrame(raf);
146
+ window.removeEventListener('scroll', position, true);
147
+ window.removeEventListener('resize', position);
148
+ };
149
+ });
150
+ </script>
151
+
152
+ <button
153
+ type="button"
154
+ class="menu-trigger {triggerClass}"
155
+ aria-haspopup="menu"
156
+ aria-expanded={open}
157
+ aria-label={triggerLabel}
158
+ bind:this={btnEl}
159
+ onclick={toggle}
160
+ onkeydown={onTriggerKeydown}
161
+ >
162
+ {@render trigger()}
163
+ </button>
164
+
165
+ {#if open}
166
+ <div
167
+ class="menu"
168
+ role="menu"
169
+ tabindex="-1"
170
+ aria-label={header}
171
+ style="min-width: {menuMinWidth};"
172
+ bind:this={menuEl}
173
+ onkeydown={onMenuKeydown}
174
+ >
175
+ <div class="menu-header" role="presentation">{header}</div>
176
+ {@render children({ close })}
177
+ </div>
178
+ {/if}
179
+
180
+ <style>
181
+ .menu-trigger {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ gap: var(--ui-space-4);
186
+ padding: 0;
187
+ background: transparent;
188
+ border: 0;
189
+ color: inherit;
190
+ font: inherit;
191
+ line-height: 1;
192
+ cursor: pointer;
193
+ }
194
+
195
+ .menu-trigger:focus-visible {
196
+ outline: 2px solid var(--ui-border-higher);
197
+ outline-offset: 2px;
198
+ }
199
+
200
+ .menu {
201
+ position: fixed;
202
+ top: 0;
203
+ left: 0;
204
+ max-width: calc(100vw - var(--ui-space-24));
205
+ background: var(--ui-surface-higher);
206
+ border: 1px solid var(--ui-border-high);
207
+ border-radius: var(--ui-radius-lg);
208
+ box-shadow: var(--ui-shadow-lg);
209
+ z-index: 1000;
210
+ overflow: hidden;
211
+ visibility: hidden;
212
+ }
213
+
214
+ .menu-header {
215
+ padding: var(--ui-space-8) var(--ui-space-12);
216
+ border-bottom: 1px solid var(--ui-border-low);
217
+ color: var(--ui-text-primary);
218
+ font-size: var(--ui-font-size-xs);
219
+ font-weight: var(--ui-font-weight-semibold);
220
+ line-height: 1.2;
221
+ }
222
+
223
+ /* Roving focus is the menu's selection cursor; the inset ring keeps it
224
+ inside the clipped rounded corners. */
225
+ .menu :global(button:focus-visible) {
226
+ background: var(--ui-hover);
227
+ outline: 2px solid var(--ui-border-higher);
228
+ outline-offset: -2px;
229
+ }
230
+ </style>
@@ -1059,7 +1059,6 @@
1059
1059
  font-size: var(--ui-font-size-xs);
1060
1060
  font-family: var(--ui-font-mono);
1061
1061
  color: var(--ui-text-tertiary);
1062
- text-transform: uppercase;
1063
1062
  border-top: 1px solid var(--ui-border-low);
1064
1063
  }
1065
1064
 
@@ -204,10 +204,9 @@
204
204
  /* Subsection title (used by Spacing & Borders) */
205
205
  .subsection-title {
206
206
  margin: var(--ui-space-16) 0 var(--ui-space-8);
207
- font-size: var(--ui-font-size-xs);
208
- font-weight: var(--ui-font-weight-semibold);
209
- color: var(--ui-text-tertiary);
210
- text-transform: uppercase;
207
+ font-size: var(--ui-font-size-xl);
208
+ font-weight: var(--ui-font-weight-bold);
209
+ color: var(--ui-text-primary);
211
210
  }
212
211
 
213
212
  .subsection-title:first-child {
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import type { AxisStatus } from '../../core/palettes/colorHarmony';
3
+
4
+ interface Props {
5
+ index: number;
6
+ status: AxisStatus;
7
+ /** The axis carries the family selected across the Colors surfaces. */
8
+ selected?: boolean;
9
+ /** Rendered over the swatch badge's scrim rather than an editor surface. */
10
+ scrim?: boolean;
11
+ }
12
+
13
+ let { index, status, selected = false, scrim = false }: Props = $props();
14
+ </script>
15
+
16
+ <!-- Carries no positioning: the wheel places it absolutely, the rows inline. -->
17
+ <span class="numeral" class:filled={status === 'on-wheel'} class:selected class:scrim>{index + 1}</span>
18
+
19
+ <style>
20
+ /* Hollow is the resting shape; filled means the axis has a rail on the wheel
21
+ right now. An unused axis is hollow too, but it never holds a family, so a
22
+ hollow numeral beside a color always reads "bound, off the wheel". */
23
+ .numeral {
24
+ box-sizing: border-box;
25
+ flex: none;
26
+ display: inline-flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ /* Callers that need a bigger numeral set these two; the row default is the
30
+ size the wheel and the axes list share. */
31
+ width: var(--numeral-size, 1rem);
32
+ height: var(--numeral-size, 1rem);
33
+ border: 1px dashed var(--ui-text-muted);
34
+ border-radius: var(--ui-radius-full);
35
+ color: var(--ui-text-muted);
36
+ font-size: var(--numeral-font-size, var(--ui-font-size-xs));
37
+ font-weight: var(--ui-font-weight-semibold);
38
+ /* No tabular figures: the box is a fixed circle, so nothing needs aligning,
39
+ and a padded tabular "1" sits off-center inside its own advance. */
40
+ line-height: 1;
41
+ user-select: none;
42
+ }
43
+
44
+ /* The scrim composites with the swatch fill beneath it, which lands close
45
+ enough to the muted token to erase the ring and the digit. */
46
+ .numeral.scrim:not(.filled) {
47
+ border-color: var(--ui-text-secondary);
48
+ color: var(--ui-text-secondary);
49
+ }
50
+
51
+ .numeral.filled {
52
+ border-style: solid;
53
+ border-color: var(--ui-border-higher);
54
+ background: var(--ui-surface-high);
55
+ color: var(--ui-text-secondary);
56
+ }
57
+
58
+ .numeral.filled.selected {
59
+ background: var(--ui-text-primary);
60
+ border-color: var(--ui-text-primary);
61
+ color: var(--ui-surface-lowest);
62
+ }
63
+
64
+ /* Selection raises contrast but never fills: filled is reserved for "has a
65
+ rail", so inverting here would make the list contradict the wheel. */
66
+ .numeral.selected:not(.filled) {
67
+ border-color: var(--ui-text-primary);
68
+ color: var(--ui-text-primary);
69
+ }
70
+ </style>
@@ -93,8 +93,6 @@
93
93
  font-size: var(--ui-font-size-xs);
94
94
  font-weight: var(--ui-font-weight-semibold);
95
95
  color: var(--ui-text-tertiary);
96
- text-transform: uppercase;
97
- letter-spacing: 0.04em;
98
96
  }
99
97
 
100
98
  .row-value {
@@ -2,18 +2,14 @@
2
2
  import { oklchToHexClamped } from '../../core/palettes/oklch';
3
3
  import { palettesToVars } from '../../core/palettes/paletteDerivation';
4
4
  import { contrastRatio } from '../../core/palettes/contrast';
5
- import type { NeutralTextRecommendation } from '../../core/palettes/recommendText';
6
5
  import type { PaletteConfig } from '../../core/themes/themeTypes';
7
6
 
8
7
  interface Props {
9
8
  /** Every family's config, spec defaults filled in (palettesWithDefaults). */
10
9
  full: Record<string, PaletteConfig>;
11
- /** Suggested neutral text hierarchy — read out here, acted on from the
12
- * section's action pills. The story itself stays non-interactive. */
13
- rec: NeutralTextRecommendation;
14
10
  }
15
11
 
16
- let { full, rec }: Props = $props();
12
+ let { full }: Props = $props();
17
13
 
18
14
  // 60-30-10 as an area composition: the background surface is the dominant 60%
19
15
  // field (the page), and Brand/Accent/Special are the tones shown against it.
@@ -24,6 +20,8 @@
24
20
  { label: 'Special', ns: 'special', share: 2 },
25
21
  ] as const;
26
22
 
23
+ const TEXT_STEPS = ['primary', 'secondary', 'tertiary'] as const;
24
+
27
25
  const FUNCTIONAL = [
28
26
  { label: 'Info', ns: 'info' },
29
27
  { label: 'Success', ns: 'success' },
@@ -69,8 +67,6 @@
69
67
  return r === null ? null : `${(Math.floor(r * 10) / 10).toFixed(1)}:1`;
70
68
  }
71
69
 
72
- let bgRatio = $derived(ratioOnBg('--text-primary'));
73
-
74
70
  // Inverted is measured against the fill it sits on (primary), not the page:
75
71
  // the pill below IS that pairing, so the number describes what is shown. A
76
72
  // mid-lightness primary makes this pairing inherently low-contrast — the
@@ -86,19 +82,20 @@
86
82
 
87
83
  <div class="story" style={storyVars}>
88
84
  <div class="field">
89
- <div class="field-head">
90
- <span class="field-label">Canvas</span>
91
- {#if fmt(bgRatio)}<span class="ratio">{fmt(bgRatio)}</span>{/if}
85
+ <span class="field-label">Canvas</span>
86
+
87
+ <!-- Each step renders in its own token, so the word IS the sample and the
88
+ ratio beside it measures exactly what you are reading. -->
89
+ <div class="text-steps">
90
+ {#each TEXT_STEPS as step (step)}
91
+ {@const r = fmt(ratioOnBg(`--text-${step}`))}
92
+ <p class="text-step" style:color="var(--text-{step})">
93
+ {step}
94
+ {#if r}<span class="ratio">{r}</span>{/if}
95
+ </p>
96
+ {/each}
92
97
  </div>
93
- <p class="body">The dominant surface. Body text sits here.</p>
94
- <p class="secondary">
95
- Secondary text follows the same field.
96
- {#if fmt(ratioOnBg('--text-secondary'))}<span class="ratio">{fmt(ratioOnBg('--text-secondary'))}</span>{/if}
97
- </p>
98
- <p class="tertiary">
99
- Tertiary text steps back once more.
100
- {#if fmt(ratioOnBg('--text-tertiary'))}<span class="ratio">{fmt(ratioOnBg('--text-tertiary'))}</span>{/if}
101
- </p>
98
+
102
99
  <p class="inverted-row">
103
100
  <span class="inverted-pill">
104
101
  Inverted text flips onto primary.
@@ -106,18 +103,6 @@
106
103
  </span>
107
104
  </p>
108
105
 
109
- <!-- Readout only: applied from the section's actions, not from here. -->
110
- <div class="suggest-row" title="What “Even neutral steps” would set these three text steps to">
111
- <span class="suggest-label">Suggested steps</span>
112
- {#each rec.suggestions as s (s.step)}
113
- <span class="suggest-entry" class:dim={!s.differs}>
114
- <span class="fn-dot" style:--dot={s.suggested.hex}></span>
115
- <span class="suggest-step">{s.step}</span>
116
- <span class="ratio">{fmt(contrastRatio(s.suggested.hex, seedHex('Canvas')))}</span>
117
- </span>
118
- {/each}
119
- </div>
120
-
121
106
  <div class="functional-row">
122
107
  {#each FUNCTIONAL as fn (fn.ns)}
123
108
  {@const r = fmt(ratioOnBg(`--text-${fn.ns}`))}
@@ -172,36 +157,11 @@
172
157
  color: var(--text-primary);
173
158
  }
174
159
 
175
- .field-head {
176
- display: flex;
177
- align-items: baseline;
178
- justify-content: space-between;
179
- gap: var(--ui-space-8);
180
- }
181
-
182
160
  .field-label {
183
161
  font-size: var(--ui-font-size-md);
184
162
  font-weight: var(--ui-font-weight-semibold);
185
163
  }
186
164
 
187
- .body {
188
- margin: 0;
189
- font-size: var(--ui-font-size-md);
190
- color: var(--text-primary);
191
- }
192
-
193
- .secondary {
194
- margin: 0;
195
- font-size: var(--ui-font-size-md);
196
- color: var(--text-secondary);
197
- }
198
-
199
- .tertiary {
200
- margin: 0;
201
- font-size: var(--ui-font-size-md);
202
- color: var(--text-tertiary);
203
- }
204
-
205
165
  .inverted-row {
206
166
  margin: var(--ui-space-8) 0 0;
207
167
  }
@@ -219,31 +179,18 @@
219
179
  font-size: var(--ui-font-size-md);
220
180
  }
221
181
 
222
- .suggest-row {
182
+ .text-steps {
223
183
  display: flex;
224
- align-items: center;
225
- flex-wrap: wrap;
226
- gap: var(--ui-space-8);
227
- margin-top: var(--ui-space-12);
228
- }
229
-
230
- .suggest-label {
231
- font-size: var(--ui-font-size-md);
232
- font-weight: var(--ui-font-weight-semibold);
233
- opacity: 0.6;
234
- }
235
-
236
- .suggest-entry {
237
- display: flex;
238
- align-items: center;
184
+ flex-direction: column;
185
+ align-items: flex-start;
239
186
  gap: var(--ui-space-6);
240
187
  }
241
188
 
242
- .suggest-entry.dim {
243
- opacity: 0.45;
244
- }
245
-
246
- .suggest-step {
189
+ .text-step {
190
+ display: flex;
191
+ align-items: baseline;
192
+ gap: var(--ui-space-8);
193
+ margin: 0;
247
194
  font-size: var(--ui-font-size-md);
248
195
  }
249
196