@marlinjai/clearify 1.5.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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/bin/clearify.js +2 -0
  4. package/dist/node/chunk-5TD7NQIW.js +25 -0
  5. package/dist/node/chunk-B2Q23JW3.js +55 -0
  6. package/dist/node/chunk-CQ4MNGBE.js +301 -0
  7. package/dist/node/chunk-GFD54GNO.js +223 -0
  8. package/dist/node/chunk-IBK35HZR.js +194 -0
  9. package/dist/node/chunk-L24ILRSX.js +125 -0
  10. package/dist/node/chunk-NXQNNLGC.js +395 -0
  11. package/dist/node/chunk-PRTER35L.js +48 -0
  12. package/dist/node/chunk-SCZZB7OE.js +9 -0
  13. package/dist/node/chunk-V7LLYIRO.js +8 -0
  14. package/dist/node/chunk-WT5W333R.js +136 -0
  15. package/dist/node/cli/index.d.ts +2 -0
  16. package/dist/node/cli/index.js +41 -0
  17. package/dist/node/core/config.d.ts +9 -0
  18. package/dist/node/core/config.js +16 -0
  19. package/dist/node/core/mermaid-renderer.d.ts +22 -0
  20. package/dist/node/core/mermaid-renderer.js +125 -0
  21. package/dist/node/core/mermaid-utils.d.ts +3 -0
  22. package/dist/node/core/mermaid-utils.js +6 -0
  23. package/dist/node/core/navigation.d.ts +2 -0
  24. package/dist/node/core/navigation.js +13 -0
  25. package/dist/node/core/openapi-parser.d.ts +14 -0
  26. package/dist/node/core/openapi-parser.js +6 -0
  27. package/dist/node/core/remark-mermaid.d.ts +10 -0
  28. package/dist/node/core/remark-mermaid.js +11 -0
  29. package/dist/node/core/search.d.ts +31 -0
  30. package/dist/node/core/search.js +6 -0
  31. package/dist/node/node/build.d.ts +3 -0
  32. package/dist/node/node/build.js +14 -0
  33. package/dist/node/node/check.d.ts +3 -0
  34. package/dist/node/node/check.js +10 -0
  35. package/dist/node/node/index.d.ts +11 -0
  36. package/dist/node/node/index.js +108 -0
  37. package/dist/node/node/init.d.ts +6 -0
  38. package/dist/node/node/init.js +6 -0
  39. package/dist/node/presets/nestjs.d.ts +15 -0
  40. package/dist/node/presets/nestjs.js +98 -0
  41. package/dist/node/types/index.d.ts +79 -0
  42. package/dist/node/types/index.js +6 -0
  43. package/dist/node/vite-plugin/index.d.ts +13 -0
  44. package/dist/node/vite-plugin/index.js +11 -0
  45. package/package.json +94 -0
  46. package/src/client/App.tsx +101 -0
  47. package/src/client/Page.tsx +15 -0
  48. package/src/client/entry-server.tsx +79 -0
  49. package/src/client/index.html +18 -0
  50. package/src/client/main.tsx +11 -0
  51. package/src/theme/CodeBlock.tsx +103 -0
  52. package/src/theme/Content.tsx +32 -0
  53. package/src/theme/Footer.tsx +53 -0
  54. package/src/theme/Head.tsx +80 -0
  55. package/src/theme/HeadContext.tsx +32 -0
  56. package/src/theme/Header.tsx +177 -0
  57. package/src/theme/Layout.tsx +44 -0
  58. package/src/theme/MDXComponents.tsx +40 -0
  59. package/src/theme/NotFound.tsx +246 -0
  60. package/src/theme/Search.tsx +359 -0
  61. package/src/theme/Sidebar.tsx +325 -0
  62. package/src/theme/TableOfContents.tsx +153 -0
  63. package/src/theme/ThemeProvider.tsx +77 -0
  64. package/src/theme/components/Accordion.tsx +109 -0
  65. package/src/theme/components/Badge.tsx +72 -0
  66. package/src/theme/components/Breadcrumbs.tsx +88 -0
  67. package/src/theme/components/Callout.tsx +115 -0
  68. package/src/theme/components/Card.tsx +103 -0
  69. package/src/theme/components/CodeGroup.tsx +79 -0
  70. package/src/theme/components/Columns.tsx +42 -0
  71. package/src/theme/components/Frame.tsx +55 -0
  72. package/src/theme/components/Mermaid.tsx +99 -0
  73. package/src/theme/components/MermaidStatic.tsx +32 -0
  74. package/src/theme/components/OpenAPI.tsx +160 -0
  75. package/src/theme/components/OpenAPIPage.tsx +16 -0
  76. package/src/theme/components/Steps.tsx +76 -0
  77. package/src/theme/components/Tabs.tsx +75 -0
  78. package/src/theme/components/Tooltip.tsx +108 -0
  79. package/src/theme/components/index.ts +14 -0
  80. package/src/theme/styles/globals.css +363 -0
@@ -0,0 +1,177 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { useTheme } from './ThemeProvider.js';
4
+ import { Search } from './Search.js';
5
+
6
+ interface HeaderProps {
7
+ name: string;
8
+ links?: { github?: string; [key: string]: string | undefined };
9
+ logo?: { light?: string; dark?: string };
10
+ onMenuToggle: () => void;
11
+ }
12
+
13
+ export function Header({ name, links, logo, onMenuToggle }: HeaderProps) {
14
+ const { theme, toggleTheme } = useTheme();
15
+
16
+ return (
17
+ <header
18
+ style={{
19
+ height: 'var(--clearify-header-height)',
20
+ borderBottom: '1px solid var(--clearify-border)',
21
+ backgroundColor: 'var(--clearify-surface)',
22
+ display: 'flex',
23
+ alignItems: 'center',
24
+ padding: '0 1.5rem',
25
+ position: 'sticky',
26
+ top: 0,
27
+ zIndex: 50,
28
+ backdropFilter: 'blur(16px) saturate(180%)',
29
+ WebkitBackdropFilter: 'blur(16px) saturate(180%)',
30
+ }}
31
+ >
32
+ <button
33
+ onClick={onMenuToggle}
34
+ aria-label="Toggle menu"
35
+ style={{
36
+ display: 'none',
37
+ background: 'none',
38
+ border: 'none',
39
+ cursor: 'pointer',
40
+ padding: '0.5rem',
41
+ color: 'var(--clearify-text)',
42
+ borderRadius: 'var(--clearify-radius-sm)',
43
+ transition: 'background-color 0.15s',
44
+ }}
45
+ className="clearify-menu-btn"
46
+ onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)')}
47
+ onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = 'transparent')}
48
+ >
49
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
50
+ <line x1="4" y1="6" x2="20" y2="6" />
51
+ <line x1="4" y1="12" x2="20" y2="12" />
52
+ <line x1="4" y1="18" x2="20" y2="18" />
53
+ </svg>
54
+ </button>
55
+
56
+ <Link
57
+ to="/"
58
+ style={{
59
+ fontWeight: 700,
60
+ fontSize: '1rem',
61
+ color: 'var(--clearify-text)',
62
+ textDecoration: 'none',
63
+ marginRight: 'auto',
64
+ letterSpacing: '-0.02em',
65
+ display: 'flex',
66
+ alignItems: 'center',
67
+ gap: '0.5rem',
68
+ }}
69
+ >
70
+ {logo && (logo.light || logo.dark) ? (
71
+ <img
72
+ src={theme === 'dark' && logo.dark ? logo.dark : logo.light || logo.dark}
73
+ alt={name}
74
+ style={{ maxHeight: 28, width: 'auto', display: 'block' }}
75
+ />
76
+ ) : (
77
+ <span
78
+ style={{
79
+ display: 'inline-flex',
80
+ alignItems: 'center',
81
+ justifyContent: 'center',
82
+ width: 24,
83
+ height: 24,
84
+ borderRadius: 'var(--clearify-radius-sm)',
85
+ background: 'var(--clearify-gradient)',
86
+ color: '#fff',
87
+ fontSize: '0.6875rem',
88
+ fontWeight: 800,
89
+ }}
90
+ >
91
+ {name.charAt(0).toUpperCase()}
92
+ </span>
93
+ )}
94
+ {name}
95
+ </Link>
96
+
97
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.375rem' }}>
98
+ <Search />
99
+
100
+ {links?.github && (
101
+ <a
102
+ href={links.github}
103
+ target="_blank"
104
+ rel="noopener noreferrer"
105
+ aria-label="GitHub"
106
+ style={{
107
+ display: 'inline-flex',
108
+ alignItems: 'center',
109
+ justifyContent: 'center',
110
+ width: 36,
111
+ height: 36,
112
+ borderRadius: 'var(--clearify-radius-sm)',
113
+ color: 'var(--clearify-text-secondary)',
114
+ transition: 'color 0.15s, background-color 0.15s',
115
+ }}
116
+ onMouseEnter={(e) => {
117
+ e.currentTarget.style.color = 'var(--clearify-text)';
118
+ e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
119
+ }}
120
+ onMouseLeave={(e) => {
121
+ e.currentTarget.style.color = 'var(--clearify-text-secondary)';
122
+ e.currentTarget.style.backgroundColor = 'transparent';
123
+ }}
124
+ >
125
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
126
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
127
+ </svg>
128
+ </a>
129
+ )}
130
+
131
+ <button
132
+ onClick={toggleTheme}
133
+ style={{
134
+ display: 'inline-flex',
135
+ alignItems: 'center',
136
+ justifyContent: 'center',
137
+ width: 36,
138
+ height: 36,
139
+ background: 'none',
140
+ border: 'none',
141
+ cursor: 'pointer',
142
+ borderRadius: 'var(--clearify-radius-sm)',
143
+ color: 'var(--clearify-text-secondary)',
144
+ transition: 'color 0.15s, background-color 0.15s',
145
+ }}
146
+ aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
147
+ onMouseEnter={(e) => {
148
+ e.currentTarget.style.color = 'var(--clearify-text)';
149
+ e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
150
+ }}
151
+ onMouseLeave={(e) => {
152
+ e.currentTarget.style.color = 'var(--clearify-text-secondary)';
153
+ e.currentTarget.style.backgroundColor = 'transparent';
154
+ }}
155
+ >
156
+ {theme === 'light' ? (
157
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
158
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
159
+ </svg>
160
+ ) : (
161
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
162
+ <circle cx="12" cy="12" r="5" />
163
+ <line x1="12" y1="1" x2="12" y2="3" />
164
+ <line x1="12" y1="21" x2="12" y2="23" />
165
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
166
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
167
+ <line x1="1" y1="12" x2="3" y2="12" />
168
+ <line x1="21" y1="12" x2="23" y2="12" />
169
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
170
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
171
+ </svg>
172
+ )}
173
+ </button>
174
+ </div>
175
+ </header>
176
+ );
177
+ }
@@ -0,0 +1,44 @@
1
+ import React, { useState } from 'react';
2
+ import { Header } from './Header.js';
3
+ import { Sidebar } from './Sidebar.js';
4
+ import { Content } from './Content.js';
5
+ import { TableOfContents } from './TableOfContents.js';
6
+ import { Footer } from './Footer.js';
7
+ import { ThemeProvider } from './ThemeProvider.js';
8
+ import type { ClearifyConfig, SectionNavigation } from '../types/index.js';
9
+
10
+ interface LayoutProps {
11
+ config: ClearifyConfig;
12
+ sections: SectionNavigation[];
13
+ children: React.ReactNode;
14
+ }
15
+
16
+ export function Layout({ config, sections, children }: LayoutProps) {
17
+ const [sidebarOpen, setSidebarOpen] = useState(false);
18
+
19
+ return (
20
+ <ThemeProvider mode={config.theme.mode}>
21
+ {/* Subtle gradient mesh background */}
22
+ <div className="clearify-bg-mesh" aria-hidden="true" />
23
+
24
+ <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
25
+ <Header
26
+ name={config.name}
27
+ links={config.links}
28
+ logo={config.logo}
29
+ onMenuToggle={() => setSidebarOpen(!sidebarOpen)}
30
+ />
31
+ <div style={{ display: 'flex', flex: 1 }}>
32
+ <Sidebar
33
+ sections={sections}
34
+ open={sidebarOpen}
35
+ onClose={() => setSidebarOpen(false)}
36
+ />
37
+ <Content>{children}</Content>
38
+ <TableOfContents />
39
+ </div>
40
+ <Footer name={config.name} links={config.links} />
41
+ </div>
42
+ </ThemeProvider>
43
+ );
44
+ }
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { Breadcrumbs } from './components/Breadcrumbs.js';
3
+ import { Callout } from './components/Callout.js';
4
+ import { Tabs, Tab } from './components/Tabs.js';
5
+ import { Steps, Step } from './components/Steps.js';
6
+ import { Card, CardGroup } from './components/Card.js';
7
+ import { CodeGroup } from './components/CodeGroup.js';
8
+ import { Mermaid } from './components/Mermaid.js';
9
+ import { MermaidStatic } from './components/MermaidStatic.js';
10
+ import { OpenAPI } from './components/OpenAPI.js';
11
+ import { Accordion, AccordionGroup } from './components/Accordion.js';
12
+ import { Badge } from './components/Badge.js';
13
+ import { Tooltip } from './components/Tooltip.js';
14
+ import { Columns, Column } from './components/Columns.js';
15
+ import { Frame } from './components/Frame.js';
16
+ import { CodeBlock } from './CodeBlock.js';
17
+
18
+ export const mdxComponents = {
19
+ Breadcrumbs,
20
+ Callout,
21
+ Tabs,
22
+ Tab,
23
+ Steps,
24
+ Step,
25
+ Card,
26
+ CardGroup,
27
+ CodeGroup,
28
+ Mermaid,
29
+ MermaidStatic,
30
+ OpenAPI,
31
+ Accordion,
32
+ AccordionGroup,
33
+ Badge,
34
+ Tooltip,
35
+ Columns,
36
+ Column,
37
+ Frame,
38
+ // Override default pre element with CodeBlock
39
+ pre: (props: React.HTMLAttributes<HTMLPreElement>) => <CodeBlock {...props} />,
40
+ };
@@ -0,0 +1,246 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Link, useLocation } from 'react-router-dom';
3
+ import routes from 'virtual:clearify/routes';
4
+
5
+ function levenshtein(a: string, b: string): number {
6
+ const m = a.length;
7
+ const n = b.length;
8
+ const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
9
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
10
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
11
+ for (let i = 1; i <= m; i++) {
12
+ for (let j = 1; j <= n; j++) {
13
+ dp[i][j] =
14
+ a[i - 1] === b[j - 1]
15
+ ? dp[i - 1][j - 1]
16
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
17
+ }
18
+ }
19
+ return dp[m][n];
20
+ }
21
+
22
+ function findSimilarRoutes(currentPath: string, maxResults = 5) {
23
+ const normalizedCurrent = currentPath.toLowerCase().replace(/\/+$/, '') || '/';
24
+
25
+ const scored = routes
26
+ .filter((r) => r.path !== normalizedCurrent)
27
+ .map((r) => {
28
+ const normalizedRoute = r.path.toLowerCase().replace(/\/+$/, '') || '/';
29
+ const distance = levenshtein(normalizedCurrent, normalizedRoute);
30
+ // Also check segment overlap for better relevance
31
+ const currentSegments = normalizedCurrent.split('/').filter(Boolean);
32
+ const routeSegments = normalizedRoute.split('/').filter(Boolean);
33
+ const commonSegments = currentSegments.filter((s) => routeSegments.includes(s)).length;
34
+ // Lower score = better match. Weight segment overlap heavily.
35
+ const score = distance - commonSegments * 3;
36
+ return { route: r, score, distance };
37
+ })
38
+ .sort((a, b) => a.score - b.score)
39
+ .slice(0, maxResults);
40
+
41
+ return scored;
42
+ }
43
+
44
+ function formatTitle(route: { path: string; frontmatter: { title?: string } }): string {
45
+ if (route.frontmatter.title) return route.frontmatter.title;
46
+ // Derive a title from the path
47
+ const segments = route.path.split('/').filter(Boolean);
48
+ if (segments.length === 0) return 'Home';
49
+ const last = segments[segments.length - 1];
50
+ return last
51
+ .replace(/[-_]/g, ' ')
52
+ .replace(/\b\w/g, (c) => c.toUpperCase());
53
+ }
54
+
55
+ export function NotFound() {
56
+ const location = useLocation();
57
+ const suggestions = useMemo(() => findSimilarRoutes(location.pathname), [location.pathname]);
58
+
59
+ return (
60
+ <div
61
+ style={{
62
+ textAlign: 'center',
63
+ padding: '6rem 1.5rem',
64
+ animation: 'clearify-fade-in 0.4s ease-out',
65
+ }}
66
+ >
67
+ <div
68
+ style={{
69
+ fontSize: '6rem',
70
+ fontWeight: 800,
71
+ letterSpacing: '-0.05em',
72
+ lineHeight: 1,
73
+ marginBottom: '0.75rem',
74
+ background: 'var(--clearify-gradient)',
75
+ WebkitBackgroundClip: 'text',
76
+ WebkitTextFillColor: 'transparent',
77
+ backgroundClip: 'text',
78
+ }}
79
+ >
80
+ 404
81
+ </div>
82
+ <p
83
+ style={{
84
+ fontSize: '1.125rem',
85
+ color: 'var(--clearify-text-secondary)',
86
+ marginBottom: '1rem',
87
+ fontWeight: 400,
88
+ }}
89
+ >
90
+ This page could not be found.
91
+ </p>
92
+ <p
93
+ style={{
94
+ fontSize: '0.875rem',
95
+ color: 'var(--clearify-text-tertiary, var(--clearify-text-secondary))',
96
+ marginBottom: '2rem',
97
+ fontFamily: 'var(--font-mono)',
98
+ opacity: 0.7,
99
+ }}
100
+ >
101
+ {location.pathname}
102
+ </p>
103
+
104
+ {suggestions.length > 0 && (
105
+ <div
106
+ style={{
107
+ maxWidth: '28rem',
108
+ margin: '0 auto 2.5rem',
109
+ textAlign: 'left',
110
+ }}
111
+ >
112
+ <p
113
+ style={{
114
+ fontSize: '0.8125rem',
115
+ fontWeight: 600,
116
+ color: 'var(--clearify-text-secondary)',
117
+ marginBottom: '0.75rem',
118
+ textTransform: 'uppercase',
119
+ letterSpacing: '0.05em',
120
+ }}
121
+ >
122
+ Did you mean?
123
+ </p>
124
+ <div
125
+ style={{
126
+ display: 'flex',
127
+ flexDirection: 'column',
128
+ gap: '0.375rem',
129
+ }}
130
+ >
131
+ {suggestions.map(({ route }) => (
132
+ <Link
133
+ key={route.path}
134
+ to={route.path}
135
+ style={{
136
+ display: 'flex',
137
+ alignItems: 'center',
138
+ gap: '0.625rem',
139
+ padding: '0.625rem 0.875rem',
140
+ borderRadius: 'var(--clearify-radius)',
141
+ border: '1px solid var(--clearify-border)',
142
+ background: 'var(--clearify-bg-secondary)',
143
+ color: 'var(--clearify-text)',
144
+ textDecoration: 'none',
145
+ fontSize: '0.875rem',
146
+ transition: 'border-color 0.2s, background 0.2s',
147
+ }}
148
+ className="clearify-404-suggestion"
149
+ >
150
+ <svg
151
+ width="14"
152
+ height="14"
153
+ viewBox="0 0 24 24"
154
+ fill="none"
155
+ stroke="var(--clearify-primary)"
156
+ strokeWidth="2"
157
+ strokeLinecap="round"
158
+ strokeLinejoin="round"
159
+ style={{ flexShrink: 0 }}
160
+ >
161
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
162
+ <polyline points="14 2 14 8 20 8" />
163
+ <line x1="16" y1="13" x2="8" y2="13" />
164
+ <line x1="16" y1="17" x2="8" y2="17" />
165
+ </svg>
166
+ <span style={{ flex: 1 }}>
167
+ <span style={{ fontWeight: 500 }}>{formatTitle(route)}</span>
168
+ <span
169
+ style={{
170
+ display: 'block',
171
+ fontSize: '0.75rem',
172
+ color: 'var(--clearify-text-secondary)',
173
+ marginTop: '0.125rem',
174
+ fontFamily: 'var(--font-mono)',
175
+ }}
176
+ >
177
+ {route.path}
178
+ </span>
179
+ </span>
180
+ <svg
181
+ width="14"
182
+ height="14"
183
+ viewBox="0 0 24 24"
184
+ fill="none"
185
+ stroke="currentColor"
186
+ strokeWidth="2"
187
+ strokeLinecap="round"
188
+ strokeLinejoin="round"
189
+ style={{ opacity: 0.4, flexShrink: 0 }}
190
+ >
191
+ <polyline points="9 18 15 12 9 6" />
192
+ </svg>
193
+ </Link>
194
+ ))}
195
+ </div>
196
+ </div>
197
+ )}
198
+
199
+ <p
200
+ style={{
201
+ fontSize: '0.8125rem',
202
+ color: 'var(--clearify-text-secondary)',
203
+ marginBottom: '1.5rem',
204
+ }}
205
+ >
206
+ Try using the search to find what you are looking for.
207
+ </p>
208
+
209
+ <Link
210
+ to="/"
211
+ style={{
212
+ display: 'inline-flex',
213
+ alignItems: 'center',
214
+ gap: '0.5rem',
215
+ padding: '0.625rem 1.5rem',
216
+ borderRadius: 'var(--clearify-radius)',
217
+ background: 'var(--clearify-gradient)',
218
+ color: '#fff',
219
+ textDecoration: 'none',
220
+ fontWeight: 600,
221
+ fontSize: '0.875rem',
222
+ boxShadow: '0 2px 8px rgba(99, 102, 241, 0.25)',
223
+ transition: 'box-shadow 0.2s, transform 0.2s',
224
+ }}
225
+ className="clearify-404-btn"
226
+ >
227
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
228
+ <line x1="19" y1="12" x2="5" y2="12" />
229
+ <polyline points="12 19 5 12 12 5" />
230
+ </svg>
231
+ Back to home
232
+
233
+ <style>{`
234
+ .clearify-404-btn:hover {
235
+ box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3) !important;
236
+ transform: translateY(-1px);
237
+ }
238
+ .clearify-404-suggestion:hover {
239
+ border-color: var(--clearify-primary) !important;
240
+ background: var(--clearify-bg-tertiary) !important;
241
+ }
242
+ `}</style>
243
+ </Link>
244
+ </div>
245
+ );
246
+ }