dsh-plugin-image-workbench 0.0.0-stage → 1.1.5
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/LICENSE +21 -0
- package/README.en.md +256 -0
- package/README.md +256 -2
- package/client.js +241 -0
- package/cordis.patch.yml +14 -0
- package/host.js +641 -0
- package/install.mjs +387 -0
- package/package.json +57 -4
- package/scripts/vendor-snapshot.py +154 -0
- package/server/access.py +26 -0
- package/server/app.py +1329 -0
- package/server/catalog.json +1 -0
- package/server/catalog.py +147 -0
- package/server/composer.py +391 -0
- package/server/drafts.py +158 -0
- package/server/history.py +575 -0
- package/server/imaging.py +172 -0
- package/server/pool.py +1335 -0
- package/server/pool_admin.py +921 -0
- package/server/pool_service.py +1073 -0
- package/server/requirements.txt +6 -0
- package/server/smoke_check.py +402 -0
- package/server/static/access.html +202 -0
- package/server/static/index.html +4725 -0
- package/vendor/chatgpt2api/README.md +13 -0
- package/vendor/chatgpt2api/SOURCE.json +11 -0
- package/vendor/chatgpt2api/runtime/uv.exe +0 -0
- package/vendor/chatgpt2api/source/.env.example +82 -0
- package/vendor/chatgpt2api/source/.python-version +1 -0
- package/vendor/chatgpt2api/source/LICENSE +21 -0
- package/vendor/chatgpt2api/source/README.md +391 -0
- package/vendor/chatgpt2api/source/VERSION +1 -0
- package/vendor/chatgpt2api/source/api/__init__.py +2 -0
- package/vendor/chatgpt2api/source/api/accounts.py +537 -0
- package/vendor/chatgpt2api/source/api/ai.py +217 -0
- package/vendor/chatgpt2api/source/api/app.py +62 -0
- package/vendor/chatgpt2api/source/api/errors.py +46 -0
- package/vendor/chatgpt2api/source/api/image_inputs.py +309 -0
- package/vendor/chatgpt2api/source/api/image_tasks.py +120 -0
- package/vendor/chatgpt2api/source/api/support.py +134 -0
- package/vendor/chatgpt2api/source/api/system.py +365 -0
- package/vendor/chatgpt2api/source/assets/atlascloud.svg +1 -0
- package/vendor/chatgpt2api/source/assets/ipwo.png +0 -0
- package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -0
- package/vendor/chatgpt2api/source/config.default.json +108 -0
- package/vendor/chatgpt2api/source/main.py +9 -0
- package/vendor/chatgpt2api/source/pyproject.toml +27 -0
- package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -0
- package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -0
- package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -0
- package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -0
- package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -0
- package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -0
- package/vendor/chatgpt2api/source/services/__init__.py +0 -0
- package/vendor/chatgpt2api/source/services/account_service.py +1757 -0
- package/vendor/chatgpt2api/source/services/auth_service.py +253 -0
- package/vendor/chatgpt2api/source/services/backup_service.py +671 -0
- package/vendor/chatgpt2api/source/services/config.py +667 -0
- package/vendor/chatgpt2api/source/services/content_filter.py +242 -0
- package/vendor/chatgpt2api/source/services/cpa_service.py +315 -0
- package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -0
- package/vendor/chatgpt2api/source/services/image_service.py +376 -0
- package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -0
- package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -0
- package/vendor/chatgpt2api/source/services/image_task_service.py +550 -0
- package/vendor/chatgpt2api/source/services/log_service.py +337 -0
- package/vendor/chatgpt2api/source/services/model_service.py +182 -0
- package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -0
- package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -0
- package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -0
- package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -0
- package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -0
- package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -0
- package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -0
- package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -0
- package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -0
- package/vendor/chatgpt2api/source/services/proxy_service.py +648 -0
- package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -0
- package/vendor/chatgpt2api/source/services/storage/base.py +38 -0
- package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -0
- package/vendor/chatgpt2api/source/services/storage/factory.py +104 -0
- package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -0
- package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -0
- package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -0
- package/vendor/chatgpt2api/source/utils/__init__.py +0 -0
- package/vendor/chatgpt2api/source/utils/helper.py +469 -0
- package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -0
- package/vendor/chatgpt2api/source/utils/log.py +110 -0
- package/vendor/chatgpt2api/source/utils/pkce.py +19 -0
- package/vendor/chatgpt2api/source/utils/pow.py +207 -0
- package/vendor/chatgpt2api/source/utils/sentinel.py +159 -0
- package/vendor/chatgpt2api/source/utils/turnstile.py +184 -0
- package/vendor/chatgpt2api/source/uv.lock +904 -0
- package/vendor/chatgpt2api/source/web/bun.lock +1501 -0
- package/vendor/chatgpt2api/source/web/components.json +24 -0
- package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -0
- package/vendor/chatgpt2api/source/web/next.config.ts +40 -0
- package/vendor/chatgpt2api/source/web/package.json +58 -0
- package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -0
- package/vendor/chatgpt2api/source/web/public/github.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/next.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/openai.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/vercel.svg +1 -0
- package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -0
- package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -0
- package/vendor/chatgpt2api/source/web/src/app/favicon.ico +0 -0
- package/vendor/chatgpt2api/source/web/src/app/globals.css +231 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -0
- package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -0
- package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -0
- package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -0
- package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -0
- package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -0
- package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -0
- package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -0
- package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -0
- package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -0
- package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -0
- package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -0
- package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -0
- package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -0
- package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -0
- package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -0
- package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -0
- package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -0
- package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -0
- package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -0
- package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -0
- package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -0
- package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -0
- package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -0
- package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -0
- package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -0
- package/vendor/chatgpt2api/source/web/tsconfig.json +42 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"bg-card text-card-foreground flex flex-col rounded-3xl border border-white/70 shadow-[0_20px_60px_-28px_rgba(25,33,61,0.18)]",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card-header"
|
|
22
|
+
className={cn("flex flex-col gap-1.5 p-6", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
data-slot="card-title"
|
|
32
|
+
className={cn("leading-none font-semibold tracking-tight", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function CardDescription({
|
|
39
|
+
className,
|
|
40
|
+
...props
|
|
41
|
+
}: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="card-description"
|
|
45
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="card-content"
|
|
55
|
+
className={cn("p-6 pt-0", className)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { Card, CardContent, CardDescription, CardHeader, CardTitle };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
3
|
+
import { Check } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Checkbox({
|
|
8
|
+
className,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
|
11
|
+
return (
|
|
12
|
+
<CheckboxPrimitive.Root
|
|
13
|
+
data-slot="checkbox"
|
|
14
|
+
className={cn(
|
|
15
|
+
"peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:bg-input/30 size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
>
|
|
20
|
+
<CheckboxPrimitive.Indicator className="flex items-center justify-center transition-none">
|
|
21
|
+
<Check className="size-3.5 text-white" />
|
|
22
|
+
</CheckboxPrimitive.Indicator>
|
|
23
|
+
</CheckboxPrimitive.Root>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { Checkbox };
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react"
|
|
4
|
+
import {
|
|
5
|
+
animate,
|
|
6
|
+
motion,
|
|
7
|
+
useInView,
|
|
8
|
+
useMotionValue,
|
|
9
|
+
useReducedMotion,
|
|
10
|
+
useTransform,
|
|
11
|
+
type HTMLMotionProps,
|
|
12
|
+
} from "motion/react"
|
|
13
|
+
|
|
14
|
+
import { cn } from "@/lib/utils"
|
|
15
|
+
|
|
16
|
+
const DEFAULT_COLORS = ["#c679c4", "#fa3d1d", "#ffb005", "#e1e1fe", "#0358f7"]
|
|
17
|
+
const BAND_HALF = 17
|
|
18
|
+
const SWEEP_START = -BAND_HALF
|
|
19
|
+
const SWEEP_END = 100 + BAND_HALF
|
|
20
|
+
|
|
21
|
+
const sweepEase = (t: number) =>
|
|
22
|
+
t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2
|
|
23
|
+
|
|
24
|
+
function buildGradient(pos: number, colors: string[], textColor: string) {
|
|
25
|
+
const bandStart = pos - BAND_HALF
|
|
26
|
+
const bandEnd = pos + BAND_HALF
|
|
27
|
+
|
|
28
|
+
if (bandStart >= 100) {
|
|
29
|
+
return `linear-gradient(90deg, ${textColor}, ${textColor})`
|
|
30
|
+
}
|
|
31
|
+
const n = colors.length
|
|
32
|
+
const parts: string[] = []
|
|
33
|
+
|
|
34
|
+
if (bandStart > 0)
|
|
35
|
+
parts.push(`${textColor} 0%`, `${textColor} ${bandStart.toFixed(2)}%`)
|
|
36
|
+
|
|
37
|
+
colors.forEach((c, i) => {
|
|
38
|
+
const pct = n === 1 ? pos : bandStart + (i / (n - 1)) * BAND_HALF * 2
|
|
39
|
+
parts.push(`${c} ${pct.toFixed(2)}%`)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (bandEnd < 100)
|
|
43
|
+
parts.push(`transparent ${bandEnd.toFixed(2)}%`, `transparent 100%`)
|
|
44
|
+
|
|
45
|
+
return `linear-gradient(90deg, ${parts.join(", ")})`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function measureWidths(el: HTMLElement, texts: string[]) {
|
|
49
|
+
const ghost = el.cloneNode() as HTMLElement
|
|
50
|
+
Object.assign(ghost.style, {
|
|
51
|
+
position: "absolute",
|
|
52
|
+
visibility: "hidden",
|
|
53
|
+
pointerEvents: "none",
|
|
54
|
+
width: "auto",
|
|
55
|
+
whiteSpace: "nowrap",
|
|
56
|
+
})
|
|
57
|
+
el.parentElement!.appendChild(ghost)
|
|
58
|
+
const widths = texts.map((t) => {
|
|
59
|
+
ghost.textContent = t
|
|
60
|
+
return ghost.getBoundingClientRect().width
|
|
61
|
+
})
|
|
62
|
+
ghost.remove()
|
|
63
|
+
return widths
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Props for {@link DiaTextReveal}.
|
|
68
|
+
*/
|
|
69
|
+
export interface DiaTextRevealProps extends Omit<
|
|
70
|
+
HTMLMotionProps<"span">,
|
|
71
|
+
"ref" | "children" | "style" | "animate" | "transition" | "color"
|
|
72
|
+
> {
|
|
73
|
+
/**
|
|
74
|
+
* Text to reveal. Pass multiple strings to rotate when {@link DiaTextRevealProps.repeat} is `true`.
|
|
75
|
+
*/
|
|
76
|
+
text: string | string[]
|
|
77
|
+
/**
|
|
78
|
+
* Colors sampled across the moving gradient band. Defaults to a built-in palette.
|
|
79
|
+
*/
|
|
80
|
+
colors?: string[]
|
|
81
|
+
/**
|
|
82
|
+
* CSS color for revealed text after the sweep and for leading/trailing regions during the animation.
|
|
83
|
+
* @defaultValue `"var(--foreground)"`
|
|
84
|
+
*/
|
|
85
|
+
textColor?: string
|
|
86
|
+
/**
|
|
87
|
+
* Duration of one sweep pass, in seconds.
|
|
88
|
+
* @defaultValue `1.5`
|
|
89
|
+
*/
|
|
90
|
+
duration?: number
|
|
91
|
+
/**
|
|
92
|
+
* Delay before the sweep starts, in seconds.
|
|
93
|
+
* @defaultValue `0`
|
|
94
|
+
*/
|
|
95
|
+
delay?: number
|
|
96
|
+
/**
|
|
97
|
+
* When `text` is an array, replay the sweep and advance to the next string after each completion.
|
|
98
|
+
* @defaultValue `false`
|
|
99
|
+
*/
|
|
100
|
+
repeat?: boolean
|
|
101
|
+
/**
|
|
102
|
+
* Pause between cycles when {@link DiaTextRevealProps.repeat} is `true`, in seconds.
|
|
103
|
+
* @defaultValue `0.5`
|
|
104
|
+
*/
|
|
105
|
+
repeatDelay?: number
|
|
106
|
+
/**
|
|
107
|
+
* If `true`, the animation starts only after the element enters the viewport.
|
|
108
|
+
* @defaultValue `true`
|
|
109
|
+
*/
|
|
110
|
+
startOnView?: boolean
|
|
111
|
+
/**
|
|
112
|
+
* Passed to `useInView`: if `true`, in-view detection fires at most once (no replay on scroll-back).
|
|
113
|
+
* @defaultValue `true`
|
|
114
|
+
*/
|
|
115
|
+
once?: boolean
|
|
116
|
+
/**
|
|
117
|
+
* Additional class names for the animated `span` (e.g. typography utilities).
|
|
118
|
+
*/
|
|
119
|
+
className?: string
|
|
120
|
+
/**
|
|
121
|
+
* When `text` has multiple entries, use the widest string’s width for layout instead of animating width per line.
|
|
122
|
+
* @defaultValue `false`
|
|
123
|
+
*/
|
|
124
|
+
fixedWidth?: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function DiaTextReveal({
|
|
128
|
+
text,
|
|
129
|
+
colors = DEFAULT_COLORS,
|
|
130
|
+
textColor = "var(--foreground)",
|
|
131
|
+
duration = 1.5,
|
|
132
|
+
delay = 0,
|
|
133
|
+
repeat = false,
|
|
134
|
+
repeatDelay = 0.5,
|
|
135
|
+
startOnView = true,
|
|
136
|
+
once = true,
|
|
137
|
+
className,
|
|
138
|
+
fixedWidth = false,
|
|
139
|
+
...props
|
|
140
|
+
}: DiaTextRevealProps) {
|
|
141
|
+
const texts = useMemo(() => (Array.isArray(text) ? text : [text]), [text])
|
|
142
|
+
const textKey = texts.join("\0")
|
|
143
|
+
const isMulti = texts.length > 1
|
|
144
|
+
const prefersReducedMotion = useReducedMotion()
|
|
145
|
+
|
|
146
|
+
const spanRef = useRef<HTMLSpanElement>(null)
|
|
147
|
+
const optsRef = useRef({
|
|
148
|
+
colors,
|
|
149
|
+
textColor,
|
|
150
|
+
duration,
|
|
151
|
+
delay,
|
|
152
|
+
repeat,
|
|
153
|
+
repeatDelay,
|
|
154
|
+
texts,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const indexRef = useRef(0)
|
|
158
|
+
const hasPlayedRef = useRef(false)
|
|
159
|
+
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined)
|
|
160
|
+
const playRef = useRef<() => void>(null!)
|
|
161
|
+
const stopRef = useRef<(() => void) | null>(null)
|
|
162
|
+
|
|
163
|
+
const [activeIndex, setActiveIndex] = useState(0)
|
|
164
|
+
const [measuredWidths, setMeasuredWidths] = useState<number[]>([])
|
|
165
|
+
|
|
166
|
+
const sweepPos = useMotionValue(SWEEP_START)
|
|
167
|
+
|
|
168
|
+
const backgroundImage = useTransform(sweepPos, (pos) =>
|
|
169
|
+
buildGradient(pos, optsRef.current.colors, optsRef.current.textColor)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const isInView = useInView(spanRef, { once, amount: 0.1 })
|
|
173
|
+
|
|
174
|
+
optsRef.current = {
|
|
175
|
+
colors,
|
|
176
|
+
textColor,
|
|
177
|
+
duration,
|
|
178
|
+
delay,
|
|
179
|
+
repeat,
|
|
180
|
+
repeatDelay,
|
|
181
|
+
texts,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
const el = spanRef.current
|
|
186
|
+
if (!el || !isMulti) return
|
|
187
|
+
setMeasuredWidths(measureWidths(el, texts))
|
|
188
|
+
}, [isMulti, textKey, texts])
|
|
189
|
+
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
playRef.current = () => {
|
|
192
|
+
const { duration, delay, repeat, repeatDelay, texts } = optsRef.current
|
|
193
|
+
|
|
194
|
+
sweepPos.set(SWEEP_START)
|
|
195
|
+
|
|
196
|
+
const controls = animate(sweepPos, SWEEP_END, {
|
|
197
|
+
duration,
|
|
198
|
+
delay,
|
|
199
|
+
ease: sweepEase,
|
|
200
|
+
onComplete() {
|
|
201
|
+
if (!repeat) return
|
|
202
|
+
timerRef.current = setTimeout(() => {
|
|
203
|
+
const next = (indexRef.current + 1) % texts.length
|
|
204
|
+
indexRef.current = next
|
|
205
|
+
setActiveIndex(next)
|
|
206
|
+
playRef.current()
|
|
207
|
+
}, repeatDelay * 1000)
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
stopRef.current = () => controls.stop()
|
|
212
|
+
}
|
|
213
|
+
}, [sweepPos])
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
if (prefersReducedMotion) {
|
|
217
|
+
sweepPos.set(SWEEP_END)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
if (startOnView && !isInView) return
|
|
221
|
+
if (once && hasPlayedRef.current) return
|
|
222
|
+
hasPlayedRef.current = true
|
|
223
|
+
playRef.current()
|
|
224
|
+
|
|
225
|
+
return () => {
|
|
226
|
+
stopRef.current?.()
|
|
227
|
+
clearTimeout(timerRef.current)
|
|
228
|
+
}
|
|
229
|
+
}, [isInView, startOnView, once, prefersReducedMotion, sweepPos])
|
|
230
|
+
|
|
231
|
+
const fixedW =
|
|
232
|
+
isMulti && fixedWidth && measuredWidths.length > 0
|
|
233
|
+
? Math.max(...measuredWidths)
|
|
234
|
+
: undefined
|
|
235
|
+
|
|
236
|
+
const animatedW =
|
|
237
|
+
isMulti && !fixedWidth && measuredWidths[activeIndex] != null
|
|
238
|
+
? measuredWidths[activeIndex]
|
|
239
|
+
: undefined
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<motion.span
|
|
243
|
+
ref={spanRef}
|
|
244
|
+
className={cn("align-bottom leading-[100%] text-inherit", className)}
|
|
245
|
+
style={{
|
|
246
|
+
transform: "translateY(-2px)",
|
|
247
|
+
color: "transparent",
|
|
248
|
+
backgroundClip: "text",
|
|
249
|
+
WebkitBackgroundClip: "text",
|
|
250
|
+
backgroundSize: "100% 100%",
|
|
251
|
+
backgroundImage,
|
|
252
|
+
...(isMulti && {
|
|
253
|
+
display: "inline-block",
|
|
254
|
+
overflow: "hidden",
|
|
255
|
+
whiteSpace: "nowrap",
|
|
256
|
+
verticalAlign: "text-center",
|
|
257
|
+
...(fixedW != null && { width: fixedW }),
|
|
258
|
+
}),
|
|
259
|
+
}}
|
|
260
|
+
animate={animatedW != null ? { width: animatedW } : undefined}
|
|
261
|
+
transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
|
|
262
|
+
{...props}
|
|
263
|
+
>
|
|
264
|
+
{texts[activeIndex]}
|
|
265
|
+
</motion.span>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
3
|
+
import { X } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
8
|
+
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DialogTrigger(
|
|
12
|
+
props: React.ComponentProps<typeof DialogPrimitive.Trigger>,
|
|
13
|
+
) {
|
|
14
|
+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function DialogPortal(
|
|
18
|
+
props: React.ComponentProps<typeof DialogPrimitive.Portal>,
|
|
19
|
+
) {
|
|
20
|
+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
24
|
+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function DialogOverlay({
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
31
|
+
return (
|
|
32
|
+
<DialogPrimitive.Overlay
|
|
33
|
+
data-slot="dialog-overlay"
|
|
34
|
+
className={cn(
|
|
35
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out fixed inset-0 z-50 bg-black/30 backdrop-blur-[2px]",
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function DialogContent({
|
|
44
|
+
className,
|
|
45
|
+
children,
|
|
46
|
+
showCloseButton = true,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
49
|
+
showCloseButton?: boolean;
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<DialogPortal>
|
|
53
|
+
<DialogOverlay />
|
|
54
|
+
<DialogPrimitive.Content
|
|
55
|
+
data-slot="dialog-content"
|
|
56
|
+
className={cn(
|
|
57
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed top-[50%] left-[50%] z-50 grid w-[min(92vw,560px)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-[28px] border border-white/80 p-6 shadow-[0_36px_120px_-45px_rgba(16,24,40,0.4)] duration-200",
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
{showCloseButton ? (
|
|
64
|
+
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-full p-1 opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:outline-none disabled:pointer-events-none">
|
|
65
|
+
<X className="size-4" />
|
|
66
|
+
<span className="sr-only">Close</span>
|
|
67
|
+
</DialogPrimitive.Close>
|
|
68
|
+
) : null}
|
|
69
|
+
</DialogPrimitive.Content>
|
|
70
|
+
</DialogPortal>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="dialog-header"
|
|
78
|
+
className={cn("flex flex-col gap-2 text-left", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
data-slot="dialog-footer"
|
|
88
|
+
className={cn(
|
|
89
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function DialogTitle({
|
|
98
|
+
className,
|
|
99
|
+
...props
|
|
100
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
101
|
+
return (
|
|
102
|
+
<DialogPrimitive.Title
|
|
103
|
+
data-slot="dialog-title"
|
|
104
|
+
className={cn("text-xl leading-none font-semibold", className)}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function DialogDescription({
|
|
111
|
+
className,
|
|
112
|
+
...props
|
|
113
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
114
|
+
return (
|
|
115
|
+
<DialogPrimitive.Description
|
|
116
|
+
data-slot="dialog-description"
|
|
117
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
Dialog,
|
|
125
|
+
DialogClose,
|
|
126
|
+
DialogContent,
|
|
127
|
+
DialogDescription,
|
|
128
|
+
DialogFooter,
|
|
129
|
+
DialogHeader,
|
|
130
|
+
DialogTitle,
|
|
131
|
+
DialogTrigger,
|
|
132
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Field({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return <div className={cn("grid gap-2", className)} {...props} />;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function FieldLabel({ className, ...props }: React.ComponentProps<"label">) {
|
|
10
|
+
return <label className={cn("text-sm font-medium text-stone-700", className)} {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Field, FieldLabel };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot="input"
|
|
10
|
+
className={cn(
|
|
11
|
+
"border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-11 w-full min-w-0 rounded-2xl border bg-white/90 px-4 py-2 text-sm shadow-sm transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:border-stone-300 focus-visible:ring-[3px] focus-visible:ring-stone-200/80",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Input };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Label as LabelPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function Label({
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<LabelPrimitive.Root
|
|
14
|
+
data-slot="label"
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Label }
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef } from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const morphTime = 1.5
|
|
8
|
+
const cooldownTime = 0.5
|
|
9
|
+
|
|
10
|
+
const useMorphingText = (texts: string[]) => {
|
|
11
|
+
const textIndexRef = useRef(0)
|
|
12
|
+
const morphRef = useRef(0)
|
|
13
|
+
const cooldownRef = useRef(0)
|
|
14
|
+
const timeRef = useRef(new Date())
|
|
15
|
+
|
|
16
|
+
const text1Ref = useRef<HTMLSpanElement>(null)
|
|
17
|
+
const text2Ref = useRef<HTMLSpanElement>(null)
|
|
18
|
+
|
|
19
|
+
const setStyles = useCallback(
|
|
20
|
+
(fraction: number) => {
|
|
21
|
+
const [current1, current2] = [text1Ref.current, text2Ref.current]
|
|
22
|
+
if (!current1 || !current2) return
|
|
23
|
+
|
|
24
|
+
current2.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`
|
|
25
|
+
current2.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`
|
|
26
|
+
|
|
27
|
+
const invertedFraction = 1 - fraction
|
|
28
|
+
current1.style.filter = `blur(${Math.min(
|
|
29
|
+
8 / invertedFraction - 8,
|
|
30
|
+
100
|
|
31
|
+
)}px)`
|
|
32
|
+
current1.style.opacity = `${Math.pow(invertedFraction, 0.4) * 100}%`
|
|
33
|
+
|
|
34
|
+
current1.textContent = texts[textIndexRef.current % texts.length]
|
|
35
|
+
current2.textContent = texts[(textIndexRef.current + 1) % texts.length]
|
|
36
|
+
},
|
|
37
|
+
[texts]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const doMorph = useCallback(() => {
|
|
41
|
+
morphRef.current -= cooldownRef.current
|
|
42
|
+
cooldownRef.current = 0
|
|
43
|
+
|
|
44
|
+
let fraction = morphRef.current / morphTime
|
|
45
|
+
|
|
46
|
+
if (fraction > 1) {
|
|
47
|
+
cooldownRef.current = cooldownTime
|
|
48
|
+
fraction = 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setStyles(fraction)
|
|
52
|
+
|
|
53
|
+
if (fraction === 1) {
|
|
54
|
+
textIndexRef.current++
|
|
55
|
+
}
|
|
56
|
+
}, [setStyles])
|
|
57
|
+
|
|
58
|
+
const doCooldown = useCallback(() => {
|
|
59
|
+
morphRef.current = 0
|
|
60
|
+
const [current1, current2] = [text1Ref.current, text2Ref.current]
|
|
61
|
+
if (current1 && current2) {
|
|
62
|
+
current2.style.filter = "none"
|
|
63
|
+
current2.style.opacity = "100%"
|
|
64
|
+
current1.style.filter = "none"
|
|
65
|
+
current1.style.opacity = "0%"
|
|
66
|
+
}
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
let animationFrameId: number
|
|
71
|
+
|
|
72
|
+
const animate = () => {
|
|
73
|
+
animationFrameId = requestAnimationFrame(animate)
|
|
74
|
+
|
|
75
|
+
const newTime = new Date()
|
|
76
|
+
const dt = (newTime.getTime() - timeRef.current.getTime()) / 1000
|
|
77
|
+
timeRef.current = newTime
|
|
78
|
+
|
|
79
|
+
cooldownRef.current -= dt
|
|
80
|
+
|
|
81
|
+
if (cooldownRef.current <= 0) doMorph()
|
|
82
|
+
else doCooldown()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
animate()
|
|
86
|
+
return () => {
|
|
87
|
+
cancelAnimationFrame(animationFrameId)
|
|
88
|
+
}
|
|
89
|
+
}, [doMorph, doCooldown])
|
|
90
|
+
|
|
91
|
+
return { text1Ref, text2Ref }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface MorphingTextProps {
|
|
95
|
+
className?: string
|
|
96
|
+
texts: string[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const Texts: React.FC<Pick<MorphingTextProps, "texts">> = ({ texts }) => {
|
|
100
|
+
const { text1Ref, text2Ref } = useMorphingText(texts)
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
<span
|
|
104
|
+
className="absolute inset-x-0 top-0 m-auto inline-block w-full"
|
|
105
|
+
ref={text1Ref}
|
|
106
|
+
/>
|
|
107
|
+
<span
|
|
108
|
+
className="absolute inset-x-0 top-0 m-auto inline-block w-full"
|
|
109
|
+
ref={text2Ref}
|
|
110
|
+
/>
|
|
111
|
+
</>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const SvgFilters: React.FC = () => (
|
|
116
|
+
<svg
|
|
117
|
+
id="filters"
|
|
118
|
+
className="fixed h-0 w-0"
|
|
119
|
+
preserveAspectRatio="xMidYMid slice"
|
|
120
|
+
>
|
|
121
|
+
<defs>
|
|
122
|
+
<filter id="threshold">
|
|
123
|
+
<feColorMatrix
|
|
124
|
+
in="SourceGraphic"
|
|
125
|
+
type="matrix"
|
|
126
|
+
values="1 0 0 0 0
|
|
127
|
+
0 1 0 0 0
|
|
128
|
+
0 0 1 0 0
|
|
129
|
+
0 0 0 255 -140"
|
|
130
|
+
/>
|
|
131
|
+
</filter>
|
|
132
|
+
</defs>
|
|
133
|
+
</svg>
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
export const MorphingText: React.FC<MorphingTextProps> = ({
|
|
137
|
+
texts,
|
|
138
|
+
className,
|
|
139
|
+
}) => (
|
|
140
|
+
<div
|
|
141
|
+
className={cn(
|
|
142
|
+
"relative mx-auto h-16 w-full max-w-3xl text-center font-sans text-[40pt] leading-none font-bold filter-[url(#threshold)_blur(0.6px)] md:h-24 lg:text-[6rem]",
|
|
143
|
+
className
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
<Texts texts={texts} />
|
|
147
|
+
<SvgFilters />
|
|
148
|
+
</div>
|
|
149
|
+
)
|