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.
Files changed (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +256 -0
  3. package/README.md +256 -2
  4. package/client.js +241 -0
  5. package/cordis.patch.yml +14 -0
  6. package/host.js +641 -0
  7. package/install.mjs +387 -0
  8. package/package.json +57 -4
  9. package/scripts/vendor-snapshot.py +154 -0
  10. package/server/access.py +26 -0
  11. package/server/app.py +1329 -0
  12. package/server/catalog.json +1 -0
  13. package/server/catalog.py +147 -0
  14. package/server/composer.py +391 -0
  15. package/server/drafts.py +158 -0
  16. package/server/history.py +575 -0
  17. package/server/imaging.py +172 -0
  18. package/server/pool.py +1335 -0
  19. package/server/pool_admin.py +921 -0
  20. package/server/pool_service.py +1073 -0
  21. package/server/requirements.txt +6 -0
  22. package/server/smoke_check.py +402 -0
  23. package/server/static/access.html +202 -0
  24. package/server/static/index.html +4725 -0
  25. package/vendor/chatgpt2api/README.md +13 -0
  26. package/vendor/chatgpt2api/SOURCE.json +11 -0
  27. package/vendor/chatgpt2api/runtime/uv.exe +0 -0
  28. package/vendor/chatgpt2api/source/.env.example +82 -0
  29. package/vendor/chatgpt2api/source/.python-version +1 -0
  30. package/vendor/chatgpt2api/source/LICENSE +21 -0
  31. package/vendor/chatgpt2api/source/README.md +391 -0
  32. package/vendor/chatgpt2api/source/VERSION +1 -0
  33. package/vendor/chatgpt2api/source/api/__init__.py +2 -0
  34. package/vendor/chatgpt2api/source/api/accounts.py +537 -0
  35. package/vendor/chatgpt2api/source/api/ai.py +217 -0
  36. package/vendor/chatgpt2api/source/api/app.py +62 -0
  37. package/vendor/chatgpt2api/source/api/errors.py +46 -0
  38. package/vendor/chatgpt2api/source/api/image_inputs.py +309 -0
  39. package/vendor/chatgpt2api/source/api/image_tasks.py +120 -0
  40. package/vendor/chatgpt2api/source/api/support.py +134 -0
  41. package/vendor/chatgpt2api/source/api/system.py +365 -0
  42. package/vendor/chatgpt2api/source/assets/atlascloud.svg +1 -0
  43. package/vendor/chatgpt2api/source/assets/ipwo.png +0 -0
  44. package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -0
  45. package/vendor/chatgpt2api/source/config.default.json +108 -0
  46. package/vendor/chatgpt2api/source/main.py +9 -0
  47. package/vendor/chatgpt2api/source/pyproject.toml +27 -0
  48. package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -0
  49. package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -0
  50. package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -0
  51. package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -0
  52. package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -0
  53. package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -0
  54. package/vendor/chatgpt2api/source/services/__init__.py +0 -0
  55. package/vendor/chatgpt2api/source/services/account_service.py +1757 -0
  56. package/vendor/chatgpt2api/source/services/auth_service.py +253 -0
  57. package/vendor/chatgpt2api/source/services/backup_service.py +671 -0
  58. package/vendor/chatgpt2api/source/services/config.py +667 -0
  59. package/vendor/chatgpt2api/source/services/content_filter.py +242 -0
  60. package/vendor/chatgpt2api/source/services/cpa_service.py +315 -0
  61. package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -0
  62. package/vendor/chatgpt2api/source/services/image_service.py +376 -0
  63. package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -0
  64. package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -0
  65. package/vendor/chatgpt2api/source/services/image_task_service.py +550 -0
  66. package/vendor/chatgpt2api/source/services/log_service.py +337 -0
  67. package/vendor/chatgpt2api/source/services/model_service.py +182 -0
  68. package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -0
  69. package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -0
  70. package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -0
  71. package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -0
  72. package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -0
  73. package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -0
  74. package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -0
  75. package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -0
  76. package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -0
  77. package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -0
  78. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -0
  79. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -0
  80. package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -0
  81. package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -0
  82. package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -0
  83. package/vendor/chatgpt2api/source/services/proxy_service.py +648 -0
  84. package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -0
  85. package/vendor/chatgpt2api/source/services/storage/base.py +38 -0
  86. package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -0
  87. package/vendor/chatgpt2api/source/services/storage/factory.py +104 -0
  88. package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -0
  89. package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -0
  90. package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -0
  91. package/vendor/chatgpt2api/source/utils/__init__.py +0 -0
  92. package/vendor/chatgpt2api/source/utils/helper.py +469 -0
  93. package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -0
  94. package/vendor/chatgpt2api/source/utils/log.py +110 -0
  95. package/vendor/chatgpt2api/source/utils/pkce.py +19 -0
  96. package/vendor/chatgpt2api/source/utils/pow.py +207 -0
  97. package/vendor/chatgpt2api/source/utils/sentinel.py +159 -0
  98. package/vendor/chatgpt2api/source/utils/turnstile.py +184 -0
  99. package/vendor/chatgpt2api/source/uv.lock +904 -0
  100. package/vendor/chatgpt2api/source/web/bun.lock +1501 -0
  101. package/vendor/chatgpt2api/source/web/components.json +24 -0
  102. package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -0
  103. package/vendor/chatgpt2api/source/web/next.config.ts +40 -0
  104. package/vendor/chatgpt2api/source/web/package.json +58 -0
  105. package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -0
  106. package/vendor/chatgpt2api/source/web/public/github.svg +1 -0
  107. package/vendor/chatgpt2api/source/web/public/next.svg +1 -0
  108. package/vendor/chatgpt2api/source/web/public/openai.svg +1 -0
  109. package/vendor/chatgpt2api/source/web/public/vercel.svg +1 -0
  110. package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -0
  111. package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -0
  112. package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -0
  113. package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -0
  114. package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -0
  115. package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -0
  116. package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -0
  117. package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -0
  118. package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -0
  119. package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -0
  120. package/vendor/chatgpt2api/source/web/src/app/favicon.ico +0 -0
  121. package/vendor/chatgpt2api/source/web/src/app/globals.css +231 -0
  122. package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -0
  123. package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -0
  124. package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -0
  125. package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -0
  126. package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -0
  127. package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -0
  128. package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -0
  129. package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -0
  130. package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -0
  131. package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -0
  132. package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -0
  133. package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -0
  134. package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -0
  135. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -0
  136. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -0
  137. package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -0
  138. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -0
  139. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -0
  140. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -0
  141. package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -0
  142. package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -0
  143. package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -0
  144. package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -0
  145. package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -0
  146. package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -0
  147. package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -0
  148. package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -0
  149. package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -0
  150. package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -0
  151. package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -0
  152. package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -0
  153. package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -0
  154. package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -0
  155. package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -0
  156. package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -0
  157. package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -0
  158. package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -0
  159. package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -0
  160. package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -0
  161. package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -0
  162. package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -0
  163. package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -0
  164. package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -0
  165. package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -0
  166. package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -0
  167. package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -0
  168. package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -0
  169. package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -0
  170. package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -0
  171. package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -0
  172. package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -0
  173. package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -0
  174. package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -0
  175. package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -0
  176. package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -0
  177. package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -0
  178. package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -0
  179. package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -0
  180. package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -0
  181. package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -0
  182. package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -0
  183. package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -0
  184. package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -0
  185. 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
+ )