@farming-labs/svelte-theme 0.0.2-beta.15 → 0.0.2-beta.18

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": "@farming-labs/svelte-theme",
3
- "version": "0.0.2-beta.15",
3
+ "version": "0.0.2-beta.18",
4
4
  "description": "Svelte UI components for @farming-labs/docs — layout, sidebar, TOC, search, and theme toggle",
5
5
  "type": "module",
6
6
  "svelte": "./src/index.js",
@@ -26,12 +26,19 @@
26
26
  "import": "./src/themes/darksharp.js",
27
27
  "default": "./src/themes/darksharp.js"
28
28
  },
29
+ "./colorful": {
30
+ "types": "./src/themes/colorful.d.ts",
31
+ "import": "./src/themes/colorful.js",
32
+ "default": "./src/themes/colorful.js"
33
+ },
29
34
  "./css": "./styles/docs.css",
30
35
  "./fumadocs/css": "./styles/docs.css",
31
36
  "./styles/pixel-border.css": "./styles/pixel-border.css",
32
37
  "./styles/darksharp.css": "./styles/darksharp.css",
33
38
  "./pixel-border/css": "./styles/pixel-border-bundle.css",
34
- "./darksharp/css": "./styles/darksharp-bundle.css"
39
+ "./darksharp/css": "./styles/darksharp-bundle.css",
40
+ "./styles/colorful.css": "./styles/colorful.css",
41
+ "./colorful/css": "./styles/colorful-bundle.css"
35
42
  },
36
43
  "typesVersions": {
37
44
  "*": {
@@ -65,8 +72,8 @@
65
72
  "dependencies": {
66
73
  "gray-matter": "^4.0.3",
67
74
  "sugar-high": "^0.9.5",
68
- "@farming-labs/docs": "0.0.2-beta.15",
69
- "@farming-labs/svelte": "0.0.2-beta.15"
75
+ "@farming-labs/docs": "0.0.2-beta.18",
76
+ "@farming-labs/svelte": "0.0.2-beta.18"
70
77
  },
71
78
  "peerDependencies": {
72
79
  "svelte": ">=5.0.0"
@@ -5,8 +5,7 @@
5
5
  let { pathname = "", entry = "docs" } = $props();
6
6
 
7
7
  let segments = $derived.by(() => {
8
- const all = pathname.split("/").filter(Boolean);
9
- return all.filter((s) => s.toLowerCase() !== entry.toLowerCase());
8
+ return pathname.split("/").filter(Boolean);
10
9
  });
11
10
 
12
11
  let parentLabel = $derived.by(() => {
@@ -25,10 +24,7 @@
25
24
 
26
25
  let parentUrl = $derived.by(() => {
27
26
  if (segments.length < 2) return "";
28
- const all = pathname.split("/").filter(Boolean);
29
- const parentSegment = segments[segments.length - 2];
30
- const parentIndex = all.indexOf(parentSegment);
31
- return "/" + all.slice(0, parentIndex + 1).join("/");
27
+ return "/" + segments.slice(0, segments.length - 1).join("/");
32
28
  });
33
29
  </script>
34
30
 
@@ -13,6 +13,10 @@
13
13
  config?.theme?.ui?.layout?.toc?.enabled ?? true
14
14
  );
15
15
 
16
+ let tocStyle = $derived(
17
+ config?.theme?.ui?.layout?.toc?.style ?? "default"
18
+ );
19
+
16
20
  let breadcrumbEnabled = $derived.by(() => {
17
21
  const bc = config?.breadcrumb;
18
22
  if (bc === undefined || bc === true) return true;
@@ -38,6 +42,7 @@
38
42
  <DocsPage
39
43
  entry={config?.entry ?? "docs"}
40
44
  {tocEnabled}
45
+ {tocStyle}
41
46
  {breadcrumbEnabled}
42
47
  previousPage={data.previousPage}
43
48
  nextPage={data.nextPage}
@@ -45,6 +50,9 @@
45
50
  lastModified={showLastModified ? data.lastModified : null}
46
51
  >
47
52
  {#snippet children()}
53
+ {#if data.description}
54
+ <p class="fd-page-description">{data.description}</p>
55
+ {/if}
48
56
  {@html data.html}
49
57
  {/snippet}
50
58
  </DocsPage>
@@ -11,6 +11,7 @@
11
11
  title = undefined,
12
12
  titleUrl = undefined,
13
13
  children,
14
+ triggerComponent = null,
14
15
  } = $props();
15
16
 
16
17
  let resolvedTitle = $derived(title ?? config?.nav?.title ?? "Docs");
@@ -143,7 +144,7 @@
143
144
  vars.push(`${COLOR_MAP[key]}: ${value};`);
144
145
  }
145
146
  if (vars.length === 0) return "";
146
- return `:root, .dark {\n ${vars.join("\n ")}\n}`;
147
+ return `.dark {\n ${vars.join("\n ")}\n}`;
147
148
  }
148
149
 
149
150
  function buildFontStyleVars(prefix, style) {
@@ -352,6 +353,7 @@
352
353
  aiLabel={config.ai.aiLabel ?? "AI"}
353
354
  position={config.ai.position ?? "bottom-right"}
354
355
  floatingStyle={config.ai.floatingStyle ?? "panel"}
356
+ {triggerComponent}
355
357
  />
356
358
  {/if}
357
359
 
@@ -6,6 +6,7 @@
6
6
 
7
7
  let {
8
8
  tocEnabled = true,
9
+ tocStyle = "default",
9
10
  breadcrumbEnabled = true,
10
11
  entry = "docs",
11
12
  previousPage = null,
@@ -143,7 +144,7 @@
143
144
 
144
145
  {#if tocEnabled}
145
146
  <aside class="fd-toc">
146
- <TableOfContents items={tocItems} />
147
+ <TableOfContents items={tocItems} {tocStyle} />
147
148
  </aside>
148
149
  {/if}
149
150
  </div>
@@ -8,6 +8,7 @@
8
8
  aiLabel = "AI",
9
9
  position = "bottom-right",
10
10
  floatingStyle = "panel",
11
+ triggerComponent = null,
11
12
  } = $props();
12
13
 
13
14
  let isOpen = $state(false);
@@ -254,17 +255,29 @@
254
255
  style={isOpen ? undefined : btnStyle}
255
256
  >
256
257
  {#if !isOpen}
257
- <button
258
- onclick={() => isOpen = true}
259
- class="fd-ai-fm-trigger-btn"
260
- aria-label="Ask {label}"
261
- >
262
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
263
- <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>
264
- <path d="M20 3v4"/><path d="M22 5h-4"/>
265
- </svg>
266
- <span>Ask {label}</span>
267
- </button>
258
+ {#if triggerComponent}
259
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
260
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
261
+ <div
262
+ onclick={() => isOpen = true}
263
+ class="fd-ai-floating-trigger"
264
+ style={btnStyle}
265
+ >
266
+ <svelte:component this={triggerComponent} />
267
+ </div>
268
+ {:else}
269
+ <button
270
+ onclick={() => isOpen = true}
271
+ class="fd-ai-fm-trigger-btn"
272
+ aria-label="Ask {label}"
273
+ >
274
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
275
+ <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>
276
+ <path d="M20 3v4"/><path d="M22 5h-4"/>
277
+ </svg>
278
+ <span>Ask {label}</span>
279
+ </button>
280
+ {/if}
268
281
  {:else}
269
282
  <div class="fd-ai-fm-input-container">
270
283
  <div class="fd-ai-fm-input-wrap">
@@ -458,17 +471,30 @@
458
471
  {/if}
459
472
 
460
473
  {#if !isOpen}
461
- <button
462
- onclick={() => isOpen = true}
463
- aria-label="Ask {label}"
464
- class="fd-ai-floating-btn"
465
- style={btnStyle}
466
- >
467
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
468
- <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>
469
- <path d="M20 3v4"/><path d="M22 5h-4"/>
470
- </svg>
471
- </button>
474
+ {#if triggerComponent}
475
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
476
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
477
+ <div
478
+ onclick={() => isOpen = true}
479
+ class="fd-ai-floating-trigger"
480
+ style={btnStyle}
481
+ >
482
+ <svelte:component this={triggerComponent} />
483
+ </div>
484
+ {:else}
485
+ <button
486
+ onclick={() => isOpen = true}
487
+ aria-label="Ask {label}"
488
+ class="fd-ai-floating-btn"
489
+ style={btnStyle}
490
+ >
491
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
492
+ <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>
493
+ <path d="M20 3v4"/><path d="M22 5h-4"/>
494
+ </svg>
495
+ <span>Ask {label}</span>
496
+ </button>
497
+ {/if}
472
498
  {/if}
473
499
  {/if}
474
500
  {/if}
@@ -1,45 +1,176 @@
1
1
  <script>
2
- import { onMount, onDestroy } from "svelte";
2
+ import { onMount, onDestroy, tick } from "svelte";
3
3
 
4
- let { items = [] } = $props();
5
- let activeId = $state("");
4
+ let { items = [], tocStyle = "default" } = $props();
5
+ let activeIds = $state(new Set());
6
6
  let observer;
7
+ let listEl;
8
+
9
+ let svgPath = $state("");
10
+ let svgWidth = $state(0);
11
+ let svgHeight = $state(0);
12
+ let thumbTop = $state(0);
13
+ let thumbHeight = $state(0);
14
+
15
+ const isDirectional = $derived(tocStyle === "directional");
16
+
17
+ function getItemOffset(depth) {
18
+ if (depth <= 2) return 14;
19
+ if (depth === 3) return 26;
20
+ return 36;
21
+ }
22
+
23
+ function getLineOffset(depth) {
24
+ return depth >= 3 ? 10 : 0;
25
+ }
26
+
27
+ function buildSvgPath() {
28
+ if (!listEl) return;
29
+ const links = listEl.querySelectorAll(".fd-toc-clerk-link");
30
+ if (links.length === 0) { svgPath = ""; return; }
31
+
32
+ let d = [];
33
+ let w = 0, h = 0;
34
+
35
+ links.forEach((el, i) => {
36
+ if (i >= items.length) return;
37
+ const depth = items[i].depth;
38
+ const x = getLineOffset(depth) + 1;
39
+ const styles = getComputedStyle(el);
40
+ const top = el.offsetTop + parseFloat(styles.paddingTop);
41
+ const bottom = el.offsetTop + el.clientHeight - parseFloat(styles.paddingBottom);
42
+ w = Math.max(x, w);
43
+ h = Math.max(h, bottom);
44
+ d.push(`${i === 0 ? "M" : "L"}${x} ${top}`);
45
+ d.push(`L${x} ${bottom}`);
46
+ });
47
+
48
+ svgPath = d.join(" ");
49
+ svgWidth = w + 1;
50
+ svgHeight = h;
51
+ }
52
+
53
+ function calcThumb() {
54
+ if (!listEl || activeIds.size === 0) {
55
+ thumbTop = 0;
56
+ thumbHeight = 0;
57
+ return;
58
+ }
59
+
60
+ let upper = Infinity, lower = 0;
61
+ for (const id of activeIds) {
62
+ const el = listEl.querySelector(`a[href="#${id}"]`);
63
+ if (!el) continue;
64
+ const styles = getComputedStyle(el);
65
+ upper = Math.min(upper, el.offsetTop + parseFloat(styles.paddingTop));
66
+ lower = Math.max(lower, el.offsetTop + el.clientHeight - parseFloat(styles.paddingBottom));
67
+ }
68
+
69
+ if (upper === Infinity) {
70
+ thumbTop = 0;
71
+ thumbHeight = 0;
72
+ return;
73
+ }
74
+
75
+ thumbTop = upper;
76
+ thumbHeight = lower - upper;
77
+ }
78
+
79
+ function maskSvgUrl() {
80
+ if (!svgPath) return "none";
81
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svgWidth} ${svgHeight}"><path d="${svgPath}" stroke="black" stroke-width="1" fill="none"/></svg>`;
82
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
83
+ }
84
+
85
+ function observeHeadings() {
86
+ if (!observer) return;
87
+ observer.disconnect();
88
+ for (const item of items) {
89
+ const el = document.querySelector(item.url);
90
+ if (el) observer.observe(el);
91
+ }
92
+ }
93
+
94
+ function isActive(item) {
95
+ return activeIds.has(item.url.slice(1));
96
+ }
97
+
98
+ function hasDiagonal(index) {
99
+ if (index === 0) return false;
100
+ return items[index - 1].depth !== items[index].depth;
101
+ }
102
+
103
+ function getDiagonalCoords(index) {
104
+ const upperOffset = getLineOffset(items[index - 1].depth);
105
+ const currentOffset = getLineOffset(items[index].depth);
106
+ return { upperOffset, currentOffset };
107
+ }
108
+
109
+ function verticalLineStyle(item, index) {
110
+ const prevDepth = index > 0 ? items[index - 1].depth : item.depth;
111
+ const nextDepth = index < items.length - 1 ? items[index + 1].depth : item.depth;
112
+ return {
113
+ position: "absolute",
114
+ left: `${getLineOffset(item.depth)}px`,
115
+ top: prevDepth !== item.depth ? "6px" : "0",
116
+ bottom: nextDepth !== item.depth ? "6px" : "0",
117
+ width: "1px",
118
+ background: "hsla(0, 0%, 50%, 0.1)",
119
+ };
120
+ }
121
+
122
+ function styleObj(obj) {
123
+ return Object.entries(obj).map(([k, v]) => `${k}:${v}`).join(";");
124
+ }
7
125
 
8
126
  onMount(() => {
9
127
  observer = new IntersectionObserver(
10
128
  (entries) => {
11
129
  for (const entry of entries) {
12
130
  if (entry.isIntersecting) {
13
- activeId = entry.target.id;
131
+ activeIds.add(entry.target.id);
132
+ } else {
133
+ activeIds.delete(entry.target.id);
14
134
  }
15
135
  }
136
+ activeIds = new Set(activeIds);
16
137
  },
17
138
  { rootMargin: "-80px 0px -80% 0px" }
18
139
  );
19
-
20
140
  observeHeadings();
141
+
142
+ if (isDirectional) {
143
+ tick().then(() => {
144
+ buildSvgPath();
145
+ calcThumb();
146
+ });
147
+ }
21
148
  });
22
149
 
23
150
  $effect(() => {
24
151
  void items;
25
152
  observeHeadings();
153
+ if (isDirectional) {
154
+ tick().then(() => {
155
+ buildSvgPath();
156
+ calcThumb();
157
+ });
158
+ }
26
159
  });
27
160
 
28
- function observeHeadings() {
29
- if (!observer) return;
30
- observer.disconnect();
31
- for (const item of items) {
32
- const el = document.querySelector(item.url);
33
- if (el) observer.observe(el);
161
+ $effect(() => {
162
+ void activeIds;
163
+ if (isDirectional) {
164
+ calcThumb();
34
165
  }
35
- }
166
+ });
36
167
 
37
168
  onDestroy(() => {
38
169
  observer?.disconnect();
39
170
  });
40
171
  </script>
41
172
 
42
- <div class="fd-toc-inner">
173
+ <div class="fd-toc-inner" class:fd-toc-directional={isDirectional}>
43
174
  <h3 class="fd-toc-title">
44
175
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
45
176
  <line x1="3" y1="6" x2="21" y2="6" />
@@ -50,14 +181,14 @@
50
181
  </h3>
51
182
  {#if items.length === 0}
52
183
  <p class="fd-toc-empty">No Headings</p>
53
- {:else}
184
+ {:else if !isDirectional}
54
185
  <ul class="fd-toc-list">
55
186
  {#each items as item}
56
187
  <li class="fd-toc-item">
57
188
  <a
58
189
  href={item.url}
59
190
  class="fd-toc-link"
60
- class:fd-toc-link-active={activeId === item.url.slice(1)}
191
+ class:fd-toc-link-active={isActive(item)}
61
192
  style:padding-left="{12 + (item.depth - 2) * 12}px"
62
193
  >
63
194
  {item.title}
@@ -65,5 +196,41 @@
65
196
  </li>
66
197
  {/each}
67
198
  </ul>
199
+ {:else}
200
+ <ul class="fd-toc-list fd-toc-clerk" style="position:relative;" bind:this={listEl}>
201
+ {#each items as item, index}
202
+ <li class="fd-toc-item">
203
+ <a
204
+ href={item.url}
205
+ class="fd-toc-link fd-toc-clerk-link"
206
+ data-active={isActive(item) ? "true" : undefined}
207
+ style="position:relative; padding-left:{getItemOffset(item.depth)}px; padding-top:6px; padding-bottom:6px; font-size:{item.depth <= 2 ? '14' : '13'}px; overflow-wrap:anywhere;"
208
+ >
209
+ <div style={styleObj(verticalLineStyle(item, index))}></div>
210
+
211
+ {#if hasDiagonal(index)}
212
+ {@const d = getDiagonalCoords(index)}
213
+ <svg viewBox="0 0 16 16" width="16" height="16" style="position:absolute; top:-6px; left:0;">
214
+ <line x1={d.upperOffset} y1="0" x2={d.currentOffset} y2="12" stroke="hsla(0, 0%, 50%, 0.1)" stroke-width="1" />
215
+ </svg>
216
+ {/if}
217
+
218
+ {item.title}
219
+ </a>
220
+ </li>
221
+ {/each}
222
+
223
+ {#if svgPath}
224
+ <div
225
+ class="fd-toc-clerk-mask"
226
+ style="position:absolute; left:0; top:0; width:{svgWidth}px; height:{svgHeight}px; pointer-events:none; mask-image:{maskSvgUrl()}; -webkit-mask-image:{maskSvgUrl()}; mask-repeat:no-repeat; -webkit-mask-repeat:no-repeat;"
227
+ >
228
+ <div
229
+ class="fd-toc-clerk-thumb"
230
+ style="margin-top:{thumbTop}px; height:{thumbHeight}px; background:var(--color-fd-primary); transition:all 0.15s; will-change:height,margin-top;"
231
+ ></div>
232
+ </div>
233
+ {/if}
234
+ </ul>
68
235
  {/if}
69
236
  </div>
@@ -0,0 +1,2 @@
1
+ export declare const colorful: (overrides?: { ui?: Record<string, unknown> }) => import("@farming-labs/docs").DocsTheme;
2
+ export declare const ColorfulUIDefaults: Record<string, unknown>;
@@ -0,0 +1,42 @@
1
+ import { createTheme } from "@farming-labs/docs";
2
+
3
+ const ColorfulUIDefaults = {
4
+ colors: {
5
+ primary: "hsl(40, 96%, 40%)",
6
+ background: "#ffffff",
7
+ muted: "#64748b",
8
+ border: "#e5e7eb",
9
+ },
10
+ typography: {
11
+ font: {
12
+ style: {
13
+ sans: "Inter, system-ui, sans-serif",
14
+ mono: "JetBrains Mono, monospace",
15
+ },
16
+ h1: { size: "1.875rem", weight: 700, lineHeight: "1.2", letterSpacing: "-0.02em" },
17
+ h2: { size: "1.5rem", weight: 600, lineHeight: "1.3" },
18
+ h3: { size: "1.25rem", weight: 600, lineHeight: "1.4" },
19
+ h4: { size: "1.125rem", weight: 600, lineHeight: "1.4" },
20
+ body: { size: "1rem", weight: 400, lineHeight: "1.75" },
21
+ small: { size: "0.875rem", weight: 400, lineHeight: "1.5" },
22
+ },
23
+ },
24
+ layout: {
25
+ contentWidth: 768,
26
+ sidebarWidth: 260,
27
+ toc: { enabled: true, depth: 3, style: "directional" },
28
+ header: { height: 56, sticky: true },
29
+ },
30
+ components: {
31
+ Callout: { variant: "soft", icon: true },
32
+ CodeBlock: { showCopyButton: true },
33
+ Tabs: { style: "default" },
34
+ },
35
+ };
36
+
37
+ export const colorful = createTheme({
38
+ name: "fumadocs-colorful",
39
+ ui: ColorfulUIDefaults,
40
+ });
41
+
42
+ export { ColorfulUIDefaults };
@@ -0,0 +1,2 @@
1
+ @import "./docs.css";
2
+ @import "./colorful.css";
@@ -0,0 +1,148 @@
1
+ /* @farming-labs/svelte-theme — colorful theme overrides
2
+ * Fumadocs-inspired theme with warm yellow/amber accent colors.
3
+ * Import AFTER the base theme CSS (docs.css).
4
+ */
5
+
6
+ /* ─── Colorful yellow accent overrides ────────────────────────────── */
7
+
8
+ :root {
9
+ --color-fd-primary: hsl(40, 96%, 40%);
10
+ --color-fd-primary-foreground: hsl(0, 0%, 100%);
11
+ --color-fd-ring: hsl(40, 80%, 50%);
12
+ }
13
+
14
+ .dark {
15
+ --color-fd-primary: hsl(45, 100%, 60%);
16
+ --color-fd-primary-foreground: hsl(0, 0%, 5%);
17
+ --color-fd-ring: hsl(45, 90%, 55%);
18
+ }
19
+
20
+ /* ─── Description under title ──────────────────────────────────────── */
21
+
22
+ .fd-page-description {
23
+ margin-bottom: 1rem;
24
+ font-size: 1.125rem;
25
+ line-height: 1.75;
26
+ color: var(--color-fd-muted-foreground);
27
+ }
28
+
29
+ /* ─── Sidebar dark overrides (fumadocs neutral) ────────────────────── */
30
+
31
+ .dark .fd-sidebar {
32
+ --color-fd-muted: hsl(0, 0%, 16%);
33
+ --color-fd-secondary: hsl(0, 0%, 18%);
34
+ --color-fd-muted-foreground: hsl(0, 0%, 72%);
35
+ }
36
+
37
+ /* ─── Cards (fumadocs style) ────────────────────────────────────────── */
38
+
39
+ .fd-card {
40
+ display: block;
41
+ border-radius: 0.75rem;
42
+ border: 1px solid var(--color-fd-border);
43
+ background: var(--color-fd-card);
44
+ padding: 1rem;
45
+ font-size: 0.875rem;
46
+ color: var(--color-fd-card-foreground);
47
+ box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
48
+ transition: background-color 150ms, border-color 150ms;
49
+ }
50
+
51
+ .fd-card:hover {
52
+ background: var(--color-fd-accent);
53
+ }
54
+
55
+ .fd-card-icon {
56
+ margin-bottom: 0.5rem;
57
+ width: fit-content;
58
+ border-radius: 0.375rem;
59
+ border: 1px solid var(--color-fd-border);
60
+ padding: 0.375rem;
61
+ color: var(--color-fd-muted-foreground);
62
+ }
63
+
64
+ .fd-card-title {
65
+ font-weight: 500;
66
+ }
67
+
68
+ .fd-card-description {
69
+ color: var(--color-fd-muted-foreground);
70
+ margin-top: 0.25rem;
71
+ }
72
+
73
+ .fd-cards {
74
+ display: grid;
75
+ grid-template-columns: 1fr;
76
+ gap: 1rem;
77
+ }
78
+
79
+ @media (min-width: 640px) {
80
+ .fd-cards {
81
+ grid-template-columns: repeat(2, 1fr);
82
+ }
83
+ }
84
+
85
+ /* ─── Page nav cards ───────────────────────────────────────────────── */
86
+
87
+ .fd-page-nav-card {
88
+ border-radius: 0.75rem;
89
+ }
90
+
91
+ /* ─── Inline code ──────────────────────────────────────────────────── */
92
+
93
+ .fd-docs-content :not(pre) > code {
94
+ padding: 3px;
95
+ border: 1px solid var(--color-fd-border);
96
+ font-size: 13px;
97
+ border-radius: 5px;
98
+ background: var(--color-fd-muted);
99
+ }
100
+
101
+ /* ─── Links in prose ───────────────────────────────────────────────── */
102
+
103
+ .fd-docs-content a:not(.fd-page-nav-card):not([class]) {
104
+ text-decoration: underline;
105
+ text-underline-offset: 3.5px;
106
+ text-decoration-color: var(--color-fd-primary);
107
+ text-decoration-thickness: 1.5px;
108
+ font-weight: 500;
109
+ }
110
+
111
+ /* ─── Tables (rounded) ─────────────────────────────────────────────── */
112
+
113
+ .fd-docs-content table {
114
+ border-collapse: separate;
115
+ border-spacing: 0;
116
+ background: var(--color-fd-card);
117
+ border-radius: 0.75rem;
118
+ border: 1px solid var(--color-fd-border);
119
+ overflow: hidden;
120
+ }
121
+
122
+ .fd-docs-content th {
123
+ background: var(--color-fd-muted);
124
+ font-weight: 600;
125
+ }
126
+
127
+ .fd-docs-content th,
128
+ .fd-docs-content td {
129
+ padding: 0.625rem;
130
+ border-bottom: 1px solid var(--color-fd-border);
131
+ }
132
+
133
+ .fd-docs-content tr:last-child td {
134
+ border-bottom: none;
135
+ }
136
+
137
+ /* ─── Blockquotes ──────────────────────────────────────────────────── */
138
+
139
+ .fd-docs-content blockquote {
140
+ border-left: 2px solid var(--color-fd-primary);
141
+ padding-left: 1rem;
142
+ color: var(--color-fd-foreground);
143
+ font-style: normal;
144
+ }
145
+
146
+ .fd-docs-content hr {
147
+ border-color: var(--color-fd-border);
148
+ }
package/styles/docs.css CHANGED
@@ -477,6 +477,37 @@ code, kbd, pre, samp {
477
477
  border-left-color: var(--color-fd-primary);
478
478
  }
479
479
 
480
+ /* ─── Clerk TOC (tree-line style) ────────────────────────────────────── */
481
+
482
+ .fd-toc-clerk {
483
+ border-left: none !important;
484
+ }
485
+
486
+ .fd-toc-clerk .fd-toc-link {
487
+ display: block;
488
+ border-left: none;
489
+ margin-left: 0;
490
+ color: var(--color-fd-muted-foreground);
491
+ text-decoration: none;
492
+ transition: color 0.15s;
493
+ }
494
+
495
+ .fd-toc-clerk .fd-toc-link:hover {
496
+ color: var(--color-fd-foreground);
497
+ }
498
+
499
+ .fd-toc-clerk .fd-toc-link[data-active="true"] {
500
+ color: var(--color-fd-primary);
501
+ }
502
+
503
+ .fd-toc-clerk-mask {
504
+ overflow: hidden;
505
+ }
506
+
507
+ .fd-toc-clerk-thumb {
508
+ width: 100%;
509
+ }
510
+
480
511
  @media (max-width: 1279px) {
481
512
  .fd-toc {
482
513
  display: none;
@@ -497,6 +528,15 @@ code, kbd, pre, samp {
497
528
  }
498
529
  }
499
530
 
531
+ /* ─── Page description (frontmatter) ─────────────────────────────────── */
532
+
533
+ .fd-page-description {
534
+ margin-bottom: 1rem;
535
+ font-size: 1.125rem;
536
+ line-height: 1.75;
537
+ color: var(--color-fd-muted-foreground);
538
+ }
539
+
500
540
  /* ─── Breadcrumb ─────────────────────────────────────────────────────── */
501
541
 
502
542
  .fd-breadcrumb {
@@ -1749,11 +1789,6 @@ html.dark pre.shiki {
1749
1789
 
1750
1790
  .fd-ai-floating-btn {
1751
1791
  border-radius: 26px;
1752
- box-shadow: 0 8px 32px rgba(99, 102, 241, 0.3);
1753
- }
1754
-
1755
- .fd-ai-floating-btn:hover {
1756
- box-shadow: 0 10px 40px rgba(99, 102, 241, 0.4);
1757
1792
  }
1758
1793
 
1759
1794
  .fd-ai-suggestion {
@@ -1802,24 +1837,32 @@ html.dark pre.shiki {
1802
1837
  .fd-ai-floating-btn {
1803
1838
  position: fixed;
1804
1839
  z-index: 9997;
1805
- width: 52px;
1806
- height: 52px;
1807
- border-radius: var(--radius, 26px);
1808
- border: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
1809
- background: var(--color-fd-primary, #6366f1);
1810
- color: var(--color-fd-primary-foreground, #fff);
1811
- cursor: pointer;
1812
1840
  display: flex;
1813
1841
  align-items: center;
1814
1842
  justify-content: center;
1815
- box-shadow: 0 8px 32px color-mix(in srgb, var(--color-fd-primary, #6366f1) 30%, transparent);
1816
- transition: all 200ms;
1843
+ gap: 8px;
1844
+ padding: 8px 12px;
1845
+ height: 40px;
1846
+ border-radius: 16px;
1847
+ border: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
1848
+ background: color-mix(in srgb, var(--color-fd-secondary, #f4f4f5) 80%, transparent);
1849
+ backdrop-filter: blur(4px);
1850
+ color: var(--color-fd-muted-foreground, #71717a);
1851
+ cursor: pointer;
1852
+ font-size: 14px;
1853
+ box-shadow: 0 1px 3px color-mix(in srgb, var(--color-fd-background, #000) 20%, transparent);
1854
+ transition: transform 150ms, background 150ms, color 150ms;
1817
1855
  animation: fd-ai-fade-in 300ms ease-out;
1818
1856
  }
1819
1857
 
1820
1858
  .fd-ai-floating-btn:hover {
1821
- transform: scale(1.05);
1822
- box-shadow: 0 10px 40px color-mix(in srgb, var(--color-fd-primary, #6366f1) 40%, transparent);
1859
+ background: var(--color-fd-accent);
1860
+ color: var(--color-fd-accent-foreground);
1861
+ transform: scale(1.03);
1862
+ }
1863
+
1864
+ .fd-ai-floating-btn:active {
1865
+ transform: scale(0.97);
1823
1866
  }
1824
1867
 
1825
1868
  .fd-ai-floating-trigger {
@@ -2148,24 +2191,28 @@ html.dark pre.shiki {
2148
2191
  align-items: center;
2149
2192
  justify-content: center;
2150
2193
  gap: 8px;
2151
- padding: 8px 16px;
2194
+ padding: 8px 12px;
2152
2195
  height: 40px;
2153
- border-radius: var(--radius, 16px);
2196
+ border-radius: 16px;
2154
2197
  border: 1px solid var(--color-fd-border, rgba(255, 255, 255, 0.1));
2155
- background: var(--color-fd-secondary, rgba(255, 255, 255, 0.06));
2198
+ background: color-mix(in srgb, var(--color-fd-secondary, #f4f4f5) 80%, transparent);
2199
+ backdrop-filter: blur(4px);
2156
2200
  color: var(--color-fd-muted-foreground, #71717a);
2157
2201
  font-family: inherit;
2158
2202
  font-size: 14px;
2159
2203
  cursor: pointer;
2160
- backdrop-filter: blur(8px);
2161
- box-shadow: 0 8px 32px color-mix(in srgb, var(--color-fd-background, #000) 40%, transparent);
2162
- transition: all 200ms;
2204
+ box-shadow: 0 1px 3px color-mix(in srgb, var(--color-fd-background, #000) 20%, transparent);
2205
+ transition: transform 150ms, background 150ms, color 150ms;
2163
2206
  animation: fd-ai-fade-in 300ms ease-out;
2164
2207
  white-space: nowrap;
2165
2208
  }
2166
2209
 
2167
2210
  .fd-ai-fm-trigger-btn:hover {
2168
- background: var(--color-fd-accent, rgba(255, 255, 255, 0.1));
2169
- color: var(--color-fd-accent-foreground, #fff);
2211
+ background: var(--color-fd-accent);
2212
+ color: var(--color-fd-accent-foreground);
2170
2213
  transform: scale(1.03);
2171
2214
  }
2215
+
2216
+ .fd-ai-fm-trigger-btn:active {
2217
+ transform: scale(0.97);
2218
+ }