@moldea.ai/website-ui 1.2.2 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moldea.ai/website-ui",
3
- "version": "1.2.2",
3
+ "version": "1.6.0",
4
4
  "description": "Shared Astro foundations for moldea public websites.",
5
5
  "homepage": "https://github.com/moldea-ai/packages/tree/main/projects/website-ui#readme",
6
6
  "bugs": {
@@ -47,6 +47,9 @@
47
47
  "types": "./dist/theme/index.d.ts",
48
48
  "import": "./dist/theme.js"
49
49
  },
50
+ "./accordion": {
51
+ "import": "./src/components/accordion/accordion.component.astro"
52
+ },
50
53
  "./action-button": {
51
54
  "import": "./src/components/action-button/action-button.component.astro"
52
55
  },
@@ -59,9 +62,18 @@
59
62
  "./breadcrumbs": {
60
63
  "import": "./src/components/breadcrumbs/breadcrumbs.component.astro"
61
64
  },
65
+ "./code-block": {
66
+ "import": "./src/components/code-block/code-block.component.astro"
67
+ },
68
+ "./connection-label": {
69
+ "import": "./src/components/connection-label/connection-label.component.astro"
70
+ },
62
71
  "./documentation-shell": {
63
72
  "import": "./src/components/documentation-shell/documentation-shell.component.astro"
64
73
  },
74
+ "./dialog": {
75
+ "import": "./src/components/dialog/dialog.component.astro"
76
+ },
65
77
  "./evaluation-replay": {
66
78
  "import": "./src/components/evaluation-replay/evaluation-replay.component.astro"
67
79
  },
@@ -69,6 +81,12 @@
69
81
  "types": "./dist/evaluation-replay/index.d.ts",
70
82
  "import": "./dist/evaluation-replay.js"
71
83
  },
84
+ "./file-preview": {
85
+ "import": "./src/components/file-preview/file-preview.component.astro"
86
+ },
87
+ "./hero-backdrop": {
88
+ "import": "./src/components/hero-backdrop/hero-backdrop.component.astro"
89
+ },
72
90
  "./inline-brand-text": {
73
91
  "import": "./src/components/inline-brand-text/inline-brand-text.component.astro"
74
92
  },
@@ -85,6 +103,9 @@
85
103
  "./site-footer": {
86
104
  "import": "./src/components/site-footer/site-footer.component.astro"
87
105
  },
106
+ "./result-summary": {
107
+ "import": "./src/components/result-summary/result-summary.component.astro"
108
+ },
88
109
  "./site-header": {
89
110
  "import": "./src/components/site-header/site-header.component.astro"
90
111
  },
@@ -0,0 +1,103 @@
1
+ ---
2
+ import { ChevronDown } from '@lucide/astro';
3
+ import InlineBrandText from '../inline-brand-text/inline-brand-text.component.astro';
4
+
5
+ // sibling items share a document-unique group name; at most one may be initially open
6
+ export interface Props {
7
+ id: string;
8
+ group: string;
9
+ title: string;
10
+ description?: string;
11
+ isOpen?: boolean;
12
+ }
13
+ const { id, group, title, description, isOpen = false } = Astro.props;
14
+ if (!id.trim() || !group.trim() || !title.trim())
15
+ throw new Error('Accordion requires an ID, group, and title.');
16
+ ---
17
+
18
+ <details
19
+ id={id}
20
+ name={group}
21
+ open={isOpen}
22
+ class="group/accordion min-w-0 scroll-mt-24 border-y sm:rounded-xl sm:border sm:bg-card"
23
+ data-accordion-item
24
+ >
25
+ <summary
26
+ class="flex min-h-12 list-none items-center gap-3 px-1 py-4 transition-colors outline-none hover:bg-secondary/60 focus-visible:ring-inset active:bg-secondary motion-reduce:transition-none sm:rounded-xl sm:px-5 [&::-webkit-details-marker]:hidden"
27
+ aria-labelledby={`${id}-title`}
28
+ aria-describedby={description ? `${id}-description` : undefined}
29
+ >
30
+ <span class="min-w-0 flex-1">
31
+ <span
32
+ id={`${id}-title`}
33
+ class="block text-sm leading-6 font-semibold sm:text-base"
34
+ ><InlineBrandText text={title} /></span
35
+ >
36
+ {
37
+ description && (
38
+ <span
39
+ id={`${id}-description`}
40
+ class="mt-1 block text-xs leading-5 text-muted-foreground"
41
+ >
42
+ <InlineBrandText text={description} />
43
+ </span>
44
+ )
45
+ }
46
+ </span>
47
+ <slot name="status" />
48
+ <ChevronDown
49
+ class="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-open/accordion:rotate-180 motion-reduce:transition-none"
50
+ aria-hidden="true"
51
+ />
52
+ </summary>
53
+ <div
54
+ class="accordion-panel min-w-0 px-1 pt-1 pb-5 sm:px-5"
55
+ data-accordion-panel
56
+ >
57
+ <slot />
58
+ </div>
59
+ </details>
60
+
61
+ <style>
62
+ /* Variable-height content fades without a height transition or page-wide movement. */
63
+ details[open] > .accordion-panel {
64
+ animation: accordion-reveal 160ms ease-out;
65
+ }
66
+ @keyframes accordion-reveal {
67
+ from {
68
+ opacity: 0;
69
+ }
70
+ to {
71
+ opacity: 1;
72
+ }
73
+ }
74
+ @media (prefers-reduced-motion: reduce) {
75
+ details[open] > .accordion-panel {
76
+ animation: none;
77
+ }
78
+ }
79
+ </style>
80
+
81
+ <script>
82
+ /** Reveals hash-linked content after direct, history, and Astro client navigation. */
83
+ const revealAccordionAnchor = (): void => {
84
+ let anchorId: string;
85
+ try {
86
+ anchorId = decodeURIComponent(window.location.hash.slice(1));
87
+ } catch {
88
+ return;
89
+ }
90
+ if (!anchorId) return;
91
+ const target = document.getElementById(anchorId);
92
+ const item = target?.closest<HTMLDetailsElement>('details[data-accordion-item]');
93
+ if (!item) return;
94
+ item.open = true;
95
+ // opening an earlier item can change the anchor position after native hash scrolling
96
+ requestAnimationFrame(() => {
97
+ target?.scrollIntoView({ block: 'start', behavior: 'instant' });
98
+ });
99
+ };
100
+ revealAccordionAnchor();
101
+ window.addEventListener('hashchange', revealAccordionAnchor);
102
+ document.addEventListener('astro:page-load', revealAccordionAnchor);
103
+ </script>
@@ -11,7 +11,12 @@ const { class: className, size = 'md', variant = 'primary', ...attributes } = As
11
11
  ---
12
12
 
13
13
  <a
14
- class:list={['action-control', `action-${variant}`, `action-size-${size}`, className]}
14
+ class:list={[
15
+ 'action-control',
16
+ variant === 'link' ? 'text-action-link' : `action-${variant}`,
17
+ `action-size-${size}`,
18
+ className,
19
+ ]}
15
20
  {...attributes}
16
21
  >
17
22
  <slot />
@@ -32,7 +32,7 @@ const { basePath = import.meta.env.BASE_URL, items } = Astro.props;
32
32
  )}
33
33
  {item.href ? (
34
34
  <a
35
- class="rounded-sm underline-offset-4 hover:text-foreground hover:underline"
35
+ class="plain-link"
36
36
  href={withBase(item.href, basePath)}
37
37
  >
38
38
  {item.label}
@@ -0,0 +1,22 @@
1
+ ---
2
+ import { renderCodeBlock } from '@moldea.ai/website-ui/markdown';
3
+
4
+ // highlighted source, with a panel by default or a flat surface for composition inside another panel
5
+ export interface Props {
6
+ source: string;
7
+ language?: string;
8
+ variant?: 'panel' | 'plain';
9
+ }
10
+ const { source, language, variant = 'panel' } = Astro.props;
11
+ const html = await renderCodeBlock(source, language);
12
+ ---
13
+
14
+ <div
15
+ class:list={[
16
+ 'prose-moldea min-w-0 text-sm [&_pre]:m-0 [&_pre]:text-xs [&_pre]:leading-6',
17
+ variant === 'panel'
18
+ ? '[&_pre]:p-4'
19
+ : '[&_pre]:rounded-none [&_pre]:border-0 [&_pre]:bg-transparent [&_pre]:p-0 [&_pre]:pb-1',
20
+ ]}
21
+ set:html={html}
22
+ />
@@ -0,0 +1,26 @@
1
+ ---
2
+ // visual relationship between examples; consumers supply its meaning and optional decorative icon
3
+ export interface Props {
4
+ tone?: 'danger' | 'neutral';
5
+ }
6
+ const { tone = 'neutral' } = Astro.props;
7
+ ---
8
+
9
+ <p
10
+ class:list={[
11
+ 'flex items-center justify-center gap-2 py-3 text-xs',
12
+ tone === 'danger' ? 'text-destructive' : 'text-muted-foreground',
13
+ ]}
14
+ >
15
+ {
16
+ Astro.slots.has('icon') && (
17
+ <span
18
+ class="shrink-0"
19
+ aria-hidden="true"
20
+ >
21
+ <slot name="icon" />
22
+ </span>
23
+ )
24
+ }
25
+ <span class="min-w-0"><slot /></span>
26
+ </p>
@@ -0,0 +1,266 @@
1
+ ---
2
+ import { ArrowLeft, X } from '@lucide/astro';
3
+
4
+ import ActionButton, {
5
+ type Props as IActionButtonProps,
6
+ } from '../action-button/action-button.component.astro';
7
+
8
+ // consumers own the unique document ID, trigger copy, and slotted dialog content
9
+ export interface Props {
10
+ id: string;
11
+ title: string;
12
+ description?: string;
13
+ triggerLabel: string;
14
+ triggerAriaLabel?: string;
15
+ triggerVariant?: IActionButtonProps['variant'];
16
+ triggerSize?: 'compact' | Exclude<IActionButtonProps['size'], 'icon'>;
17
+ size?: 'medium' | 'large';
18
+ closeLabel?: string;
19
+ // matches the platform's opt-in dismissal for read-only content
20
+ isOverlayCloseEnabled?: boolean;
21
+ }
22
+
23
+ const {
24
+ id,
25
+ title,
26
+ description,
27
+ triggerLabel,
28
+ triggerAriaLabel,
29
+ triggerVariant = 'outline',
30
+ triggerSize = 'compact',
31
+ size = 'medium',
32
+ closeLabel = 'Close dialog',
33
+ isOverlayCloseEnabled = false,
34
+ } = Astro.props;
35
+ ---
36
+
37
+ <div data-dialog-root>
38
+ <ActionButton
39
+ variant={triggerVariant}
40
+ size={triggerSize === 'compact' ? 'sm' : triggerSize}
41
+ class={triggerSize === 'compact' ? 'py-1 text-xs [hidden]:hidden' : '[hidden]:hidden'}
42
+ aria-label={triggerAriaLabel}
43
+ aria-controls={id}
44
+ aria-haspopup="dialog"
45
+ data-dialog-trigger
46
+ hidden
47
+ >
48
+ {triggerLabel}
49
+ </ActionButton>
50
+ <dialog
51
+ id={id}
52
+ class:list={[
53
+ 'fixed inset-0 m-0 h-dvh max-h-none w-full max-w-none grid-rows-[auto_minmax(0,1fr)] overflow-hidden border-0 bg-background p-0 text-foreground outline-none [--dialog-exit-duration:300ms] [--dialog-motion-transform:translateY(0.25rem)] backdrop:bg-black/45 open:grid sm:m-auto sm:h-fit sm:max-h-[calc(100dvh-2rem)] sm:w-[calc(100vw-2rem)] sm:rounded-xl sm:border sm:shadow-raised sm:[--dialog-exit-duration:200ms] sm:[--dialog-motion-transform:translateY(0.5rem)_scale(0.95)] sm:backdrop:backdrop-blur-xs dark:backdrop:bg-black/70',
54
+ size === 'large' ? 'sm:max-w-5xl' : 'sm:max-w-2xl',
55
+ ]}
56
+ aria-labelledby={`${id}-title`}
57
+ aria-describedby={description ? `${id}-description` : undefined}
58
+ data-website-ui-dialog
59
+ data-overlay-close-enabled={isOverlayCloseEnabled ? '' : undefined}
60
+ >
61
+ <header
62
+ class="grid grid-cols-[auto_minmax(0,1fr)] items-center gap-x-2 border-b pt-[max(0.5rem,env(safe-area-inset-top))] pr-[max(0.5rem,env(safe-area-inset-right))] pb-4 pl-[max(0.5rem,env(safe-area-inset-left))] sm:grid-cols-[minmax(0,1fr)_auto] sm:px-6 sm:py-4"
63
+ >
64
+ <div class="col-start-2 row-start-1 flex min-h-9 min-w-0 items-center sm:col-start-1">
65
+ <slot name="heading">
66
+ <h2
67
+ id={`${id}-title`}
68
+ class="min-w-0 text-lg font-semibold"
69
+ >
70
+ {title}
71
+ </h2>
72
+ </slot>
73
+ </div>
74
+ <form
75
+ method="dialog"
76
+ class="col-start-1 row-start-1 sm:col-start-2"
77
+ data-dialog-close-form
78
+ >
79
+ <ActionButton
80
+ type="submit"
81
+ variant="ghost"
82
+ size="icon"
83
+ class="size-9 sm:size-7 sm:rounded-md"
84
+ aria-label={closeLabel}
85
+ autofocus
86
+ >
87
+ <ArrowLeft
88
+ class="size-4 sm:hidden"
89
+ aria-hidden="true"
90
+ />
91
+ <X
92
+ class="hidden size-4 sm:block"
93
+ aria-hidden="true"
94
+ />
95
+ </ActionButton>
96
+ </form>
97
+ {
98
+ description && (
99
+ <p
100
+ id={`${id}-description`}
101
+ class="col-span-2 mt-1 px-2 text-sm leading-6 text-muted-foreground sm:px-0"
102
+ >
103
+ {description}
104
+ </p>
105
+ )
106
+ }
107
+ </header>
108
+ <div
109
+ class="min-h-0 min-w-0 overflow-y-auto overscroll-contain p-4 pb-[max(1rem,env(safe-area-inset-bottom))] sm:p-6"
110
+ >
111
+ <slot />
112
+ </div>
113
+ </dialog>
114
+ </div>
115
+
116
+ <script>
117
+ /** Enhances optional detail triggers while native dialogs own modality and keyboard focus. */
118
+ const initializeDialogs = (): void => {
119
+ for (const root of document.querySelectorAll<HTMLElement>('[data-dialog-root]')) {
120
+ if (root.dataset['enhanced'] === 'true') continue;
121
+ const trigger = root.querySelector<HTMLButtonElement>(':scope > [data-dialog-trigger]');
122
+ const dialog = root.querySelector<HTMLDialogElement>(':scope > dialog');
123
+ if (trigger === null || dialog === null || typeof dialog.showModal !== 'function') continue;
124
+
125
+ let closeRequest: symbol | undefined;
126
+ /**
127
+ * Keeps native modality until exit completes; navigation and reopening invalidate old exits.
128
+ * @returns A promise that resolves after dismissal or invalidation by a newer session.
129
+ */
130
+ const requestClose = async (): Promise<void> => {
131
+ if (!dialog.open || closeRequest !== undefined) return;
132
+ const request = Symbol();
133
+ closeRequest = request;
134
+ dialog.dataset['closing'] = '';
135
+ // cancellation (including reduced-motion changes) must still finish dismissal
136
+ await Promise.allSettled(dialog.getAnimations().map((animation) => animation.finished));
137
+ if (closeRequest === request && dialog.isConnected && dialog.open) dialog.close();
138
+ };
139
+ trigger.addEventListener('click', () => {
140
+ if (dialog.open) return;
141
+ closeRequest = undefined;
142
+ delete dialog.dataset['closing'];
143
+ dialog.showModal();
144
+ });
145
+ dialog.addEventListener('cancel', (event) => {
146
+ event.preventDefault();
147
+ void requestClose();
148
+ });
149
+ dialog.querySelector('[data-dialog-close-form]')?.addEventListener('submit', (event) => {
150
+ event.preventDefault();
151
+ void requestClose();
152
+ });
153
+ dialog.addEventListener('close', () => {
154
+ if (dialog.open) return;
155
+ closeRequest = undefined;
156
+ delete dialog.dataset['closing'];
157
+ });
158
+ if (dialog.hasAttribute('data-overlay-close-enabled')) {
159
+ let didPressOverlay = false;
160
+ /** Native backdrop events target the dialog, so exclude its panel bounds. */
161
+ const isOverlayEvent = (event: MouseEvent): boolean => {
162
+ if (event.target !== dialog) return false;
163
+ const bounds = dialog.getBoundingClientRect();
164
+ return (
165
+ event.clientX < bounds.left ||
166
+ event.clientX > bounds.right ||
167
+ event.clientY < bounds.top ||
168
+ event.clientY > bounds.bottom
169
+ );
170
+ };
171
+ dialog.addEventListener('pointerdown', (event) => {
172
+ didPressOverlay = event.isPrimary && event.button === 0 && isOverlayEvent(event);
173
+ });
174
+ dialog.addEventListener('pointercancel', () => {
175
+ didPressOverlay = false;
176
+ });
177
+ dialog.addEventListener('click', (event) => {
178
+ const shouldClose = didPressOverlay && event.button === 0 && isOverlayEvent(event);
179
+ didPressOverlay = false;
180
+ if (shouldClose) void requestClose();
181
+ });
182
+ dialog.addEventListener('close', () => {
183
+ didPressOverlay = false;
184
+ });
185
+ }
186
+ root.dataset['enhanced'] = 'true';
187
+ trigger.hidden = false;
188
+ }
189
+ };
190
+
191
+ initializeDialogs();
192
+ document.addEventListener('astro:page-load', initializeDialogs);
193
+ document.addEventListener('astro:before-swap', () => {
194
+ for (const dialog of document.querySelectorAll<HTMLDialogElement>(
195
+ '[data-website-ui-dialog]:modal',
196
+ )) {
197
+ dialog.close();
198
+ }
199
+ });
200
+ </script>
201
+
202
+ <style>
203
+ /* Native modality makes the page inert; also prevent background scrolling. */
204
+ :global(html:has([data-website-ui-dialog]:modal)) {
205
+ overflow: hidden;
206
+ }
207
+
208
+ /* Match platform dialog motion without animating content dimensions. */
209
+ @media (prefers-reduced-motion: no-preference) {
210
+ dialog:modal {
211
+ animation: dialog-enter 300ms ease-out both;
212
+ }
213
+
214
+ dialog:modal::backdrop {
215
+ animation: dialog-backdrop-enter 300ms ease-out both;
216
+ }
217
+
218
+ dialog[data-closing]:modal {
219
+ animation: dialog-exit var(--dialog-exit-duration) ease-in both;
220
+ }
221
+
222
+ dialog[data-closing]:modal::backdrop {
223
+ animation: dialog-backdrop-exit 200ms ease-in both;
224
+ }
225
+ }
226
+
227
+ @keyframes dialog-enter {
228
+ from {
229
+ opacity: 0;
230
+ transform: var(--dialog-motion-transform);
231
+ }
232
+ to {
233
+ opacity: 1;
234
+ transform: none;
235
+ }
236
+ }
237
+
238
+ @keyframes dialog-exit {
239
+ from {
240
+ opacity: 1;
241
+ transform: none;
242
+ }
243
+ to {
244
+ opacity: 0;
245
+ transform: var(--dialog-motion-transform);
246
+ }
247
+ }
248
+
249
+ @keyframes dialog-backdrop-enter {
250
+ from {
251
+ opacity: 0;
252
+ }
253
+ to {
254
+ opacity: 1;
255
+ }
256
+ }
257
+
258
+ @keyframes dialog-backdrop-exit {
259
+ from {
260
+ opacity: 1;
261
+ }
262
+ to {
263
+ opacity: 0;
264
+ }
265
+ }
266
+ </style>
@@ -11,10 +11,7 @@ const { headings } = Astro.props;
11
11
  {
12
12
  headings.map((heading) => (
13
13
  <a
14
- class:list={[
15
- 'text-sm leading-5 text-muted-foreground hover:text-foreground',
16
- heading.depth === 3 && 'ps-3',
17
- ]}
14
+ class:list={['plain-link text-sm leading-5', heading.depth === 3 && 'ps-3']}
18
15
  href={`#${heading.id}`}
19
16
  >
20
17
  {heading.text}
@@ -89,12 +89,7 @@ const contentGridClass = hasNavigation
89
89
  <div class="grid gap-1">
90
90
  {group.items.map((item) => (
91
91
  <a
92
- class:list={[
93
- 'rounded-lg px-3 py-2.5 text-sm font-medium',
94
- item.href === currentRoute
95
- ? 'bg-accent text-accent-foreground'
96
- : 'hover:bg-muted',
97
- ]}
92
+ class="rounded-lg navigation-link px-3 py-2.5 text-sm"
98
93
  href={withBase(item.href, basePath)}
99
94
  aria-current={item.href === currentRoute ? 'page' : undefined}
100
95
  >
@@ -144,12 +139,7 @@ const contentGridClass = hasNavigation
144
139
  <div class="grid gap-1">
145
140
  {group.items.map((item) => (
146
141
  <a
147
- class:list={[
148
- 'rounded-lg px-3 py-2 text-sm font-medium',
149
- item.href === currentRoute
150
- ? 'bg-accent text-accent-foreground'
151
- : 'text-muted-foreground hover:bg-muted hover:text-foreground',
152
- ]}
142
+ class="rounded-lg navigation-link px-3 py-2 text-sm"
153
143
  href={withBase(item.href, basePath)}
154
144
  aria-current={item.href === currentRoute ? 'page' : undefined}
155
145
  >
@@ -14,6 +14,6 @@ const html = await renderMarkdownFragment(markdown, {
14
14
  ---
15
15
 
16
16
  <div
17
- class="prose-moldea prose-moldea-wrap-code replay-markdown min-w-0"
17
+ class="prose-moldea replay-markdown min-w-0"
18
18
  set:html={html}
19
19
  />
@@ -0,0 +1,55 @@
1
+ ---
2
+ import { FileText } from '@lucide/astro';
3
+
4
+ import InlineBrandText from '../inline-brand-text/inline-brand-text.component.astro';
5
+ import type { IStatusBadgeTone } from '../status-badge/status-badge.component.astro';
6
+
7
+ // file presentation only; consumers own source selection, status, and body content
8
+ export interface Props {
9
+ path: string;
10
+ label?: string;
11
+ tone?: IStatusBadgeTone;
12
+ }
13
+
14
+ const { path, label, tone = 'neutral' } = Astro.props;
15
+ const filenameStart = path.lastIndexOf('/');
16
+ const directory = filenameStart < 0 ? '' : path.slice(0, filenameStart);
17
+ const filename = filenameStart < 0 ? path : path.slice(filenameStart);
18
+ const toneClasses = {
19
+ danger: 'border-destructive/40 bg-destructive/10',
20
+ info: 'border-info/40 bg-info/10',
21
+ neutral: 'border-border bg-muted/30',
22
+ success: 'border-border bg-secondary',
23
+ warning: 'border-warning/60 bg-warning/15',
24
+ } as const;
25
+ ---
26
+
27
+ <div class="min-w-0 overflow-hidden rounded-lg border">
28
+ <div class:list={['flex items-center gap-3 border-b px-3 py-3', toneClasses[tone]]}>
29
+ <span
30
+ class="shrink-0 text-muted-foreground"
31
+ aria-hidden="true"
32
+ >
33
+ <slot name="icon"><FileText class="size-5" /></slot>
34
+ </span>
35
+ <div class="min-w-0 flex-1">
36
+ {
37
+ label && (
38
+ <p class="mb-1 text-xs text-muted-foreground">
39
+ <InlineBrandText text={label} />
40
+ </p>
41
+ )
42
+ }
43
+ <code
44
+ class="inline-code flex w-fit max-w-full text-xs whitespace-nowrap"
45
+ title={path}
46
+ >
47
+ <span class="min-w-0 truncate">{directory}</span><span
48
+ class="max-w-full shrink-0 break-all whitespace-normal">{filename}</span
49
+ >
50
+ </code>
51
+ </div>
52
+ <slot name="status" />
53
+ </div>
54
+ <slot />
55
+ </div>
@@ -0,0 +1,9 @@
1
+ ---
2
+ // shared static brand backdrop; the consumer owns the positioned section and foreground content
3
+ ---
4
+
5
+ <div
6
+ class="pointer-events-none absolute inset-0 [background-image:radial-gradient(circle_at_70%_18%,color-mix(in_oklch,var(--accent)_60%,transparent),transparent_30%),linear-gradient(to_right,color-mix(in_oklch,var(--border)_35%,transparent)_1px,transparent_1px),linear-gradient(to_bottom,color-mix(in_oklch,var(--border)_35%,transparent)_1px,transparent_1px)] [mask-image:linear-gradient(to_bottom,black,transparent_92%)] [background-size:auto,3rem_3rem,3rem_3rem] opacity-60"
7
+ aria-hidden="true"
8
+ >
9
+ </div>
@@ -13,7 +13,7 @@ const segments = text.split(/(\bmoldea\b)/giu);
13
13
  /^moldea$/iu.test(segment) ? (
14
14
  <code
15
15
  class:list={[
16
- 'font-mono font-semibold',
16
+ 'font-mono font-semibold tracking-normal',
17
17
  variant === 'badge'
18
18
  ? 'rounded-md bg-code px-1.5 py-1 text-[0.86em] text-code-foreground'
19
19
  : 'text-[0.92em]',