@windrun-huaiin/third-ui 30.1.0 → 31.0.1

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 (103) hide show
  1. package/README.md +109 -143
  2. package/dist/ai/ai-prompt-textarea.js +5 -5
  3. package/dist/ai/ai-prompt-textarea.mjs +5 -5
  4. package/dist/clerk/clerk-auth-appearance.d.ts +13 -0
  5. package/dist/clerk/clerk-auth-appearance.js +19 -0
  6. package/dist/clerk/clerk-auth-appearance.mjs +15 -0
  7. package/dist/clerk/clerk-auth-modal-appearance.d.ts +12 -0
  8. package/dist/clerk/clerk-auth-modal-appearance.js +17 -0
  9. package/dist/clerk/clerk-auth-modal-appearance.mjs +14 -0
  10. package/dist/clerk/clerk-page-context-generator.js +3 -3
  11. package/dist/clerk/clerk-page-context-generator.mjs +3 -3
  12. package/dist/clerk/clerk-page-generator.js +4 -4
  13. package/dist/clerk/clerk-page-generator.mjs +4 -4
  14. package/dist/clerk/clerk-user-client.js +2 -1
  15. package/dist/clerk/clerk-user-client.mjs +2 -1
  16. package/dist/clerk/fingerprint/fingerprint-client.d.ts +10 -10
  17. package/dist/clerk/fingerprint/fingerprint-client.js +20 -20
  18. package/dist/clerk/fingerprint/fingerprint-client.mjs +20 -20
  19. package/dist/clerk/fingerprint/fingerprint-provider.d.ts +3 -3
  20. package/dist/clerk/fingerprint/fingerprint-provider.js +8 -8
  21. package/dist/clerk/fingerprint/fingerprint-provider.mjs +8 -8
  22. package/dist/clerk/fingerprint/fingerprint-server.d.ts +12 -12
  23. package/dist/clerk/fingerprint/fingerprint-server.js +17 -17
  24. package/dist/clerk/fingerprint/fingerprint-server.mjs +17 -17
  25. package/dist/clerk/fingerprint/fingerprint-shared.d.ts +3 -3
  26. package/dist/clerk/fingerprint/fingerprint-shared.js +10 -10
  27. package/dist/clerk/fingerprint/fingerprint-shared.mjs +10 -10
  28. package/dist/clerk/fingerprint/types.d.ts +0 -1
  29. package/dist/clerk/fingerprint/use-fingerprint.js +7 -7
  30. package/dist/clerk/fingerprint/use-fingerprint.mjs +7 -7
  31. package/dist/clerk/signin-with-fingerprint-client.d.ts +2 -2
  32. package/dist/clerk/signin-with-fingerprint-client.js +7 -6
  33. package/dist/clerk/signin-with-fingerprint-client.mjs +7 -6
  34. package/dist/clerk/signup-button-with-fingerprint-client.js +6 -4
  35. package/dist/clerk/signup-button-with-fingerprint-client.mjs +6 -4
  36. package/dist/clerk/signup-with-fingerprint-client.d.ts +2 -2
  37. package/dist/clerk/signup-with-fingerprint-client.js +7 -6
  38. package/dist/clerk/signup-with-fingerprint-client.mjs +7 -6
  39. package/dist/fuma/heavy/mermaid.js +1 -1
  40. package/dist/fuma/heavy/mermaid.mjs +1 -1
  41. package/dist/fuma/mdx/fuma-github-info.d.ts +1 -2
  42. package/dist/fuma/mdx/fuma-github-info.js +3 -6
  43. package/dist/fuma/mdx/fuma-github-info.mjs +3 -6
  44. package/dist/fuma/site-x.js +0 -1
  45. package/dist/fuma/site-x.mjs +0 -1
  46. package/dist/main/calendar/calendar-date-range-input.js +1 -1
  47. package/dist/main/calendar/calendar-date-range-input.mjs +1 -1
  48. package/dist/main/credit/credit-overview-nav-client.d.ts +12 -0
  49. package/dist/main/credit/credit-overview-nav-client.js +65 -0
  50. package/dist/main/credit/credit-overview-nav-client.mjs +63 -0
  51. package/dist/main/credit/index.d.ts +2 -0
  52. package/dist/main/credit/index.js +2 -0
  53. package/dist/main/credit/index.mjs +1 -0
  54. package/dist/main/credit/types.d.ts +8 -8
  55. package/dist/main/money-price/index.d.ts +1 -1
  56. package/dist/main/money-price/money-price-button.js +10 -10
  57. package/dist/main/money-price/money-price-button.mjs +10 -10
  58. package/dist/main/money-price/money-price-config-util.d.ts +30 -30
  59. package/dist/main/money-price/money-price-config-util.js +48 -48
  60. package/dist/main/money-price/money-price-config-util.mjs +48 -48
  61. package/dist/main/money-price/money-price-interactive.js +30 -18
  62. package/dist/main/money-price/money-price-interactive.mjs +30 -18
  63. package/dist/main/money-price/money-price-types.d.ts +7 -1
  64. package/dist/main/money-price/money-price-types.js +2 -2
  65. package/dist/main/money-price/money-price-types.mjs +2 -2
  66. package/dist/main/money-price/server.d.ts +1 -1
  67. package/dist/main/pill-select/x-pill-select.js +2 -2
  68. package/dist/main/pill-select/x-pill-select.mjs +2 -2
  69. package/dist/main/server.d.ts +1 -1
  70. package/package.json +13 -7
  71. package/src/ai/ai-prompt-textarea.tsx +6 -6
  72. package/src/clerk/clerk-auth-appearance.ts +16 -0
  73. package/src/clerk/clerk-page-context-generator.tsx +3 -5
  74. package/src/clerk/clerk-page-generator.tsx +9 -8
  75. package/src/clerk/clerk-user-client.tsx +14 -5
  76. package/src/clerk/fingerprint/fingerprint-client.ts +20 -20
  77. package/src/clerk/fingerprint/fingerprint-provider.tsx +11 -11
  78. package/src/clerk/fingerprint/fingerprint-server.ts +17 -17
  79. package/src/clerk/fingerprint/fingerprint-shared.ts +10 -10
  80. package/src/clerk/fingerprint/types.ts +0 -1
  81. package/src/clerk/fingerprint/use-fingerprint.ts +7 -7
  82. package/src/clerk/signin-with-fingerprint-client.tsx +7 -7
  83. package/src/clerk/signup-button-with-fingerprint-client.tsx +7 -5
  84. package/src/clerk/signup-with-fingerprint-client.tsx +7 -7
  85. package/src/fuma/base/custom-home-layout.tsx +4 -4
  86. package/src/fuma/heavy/mermaid.tsx +1 -1
  87. package/src/fuma/mdx/fuma-github-info.tsx +3 -8
  88. package/src/fuma/site-x.tsx +0 -1
  89. package/src/main/calendar/calendar-date-range-input.tsx +1 -1
  90. package/src/main/credit/credit-overview-nav-client.tsx +95 -0
  91. package/src/main/credit/index.ts +5 -0
  92. package/src/main/credit/types.ts +8 -8
  93. package/src/main/gallery/gallery-mobile-swiper.tsx +0 -1
  94. package/src/main/gallery/gallery-server.tsx +2 -2
  95. package/src/main/money-price/index.ts +2 -0
  96. package/src/main/money-price/money-price-button.tsx +10 -10
  97. package/src/main/money-price/money-price-config-util.ts +49 -49
  98. package/src/main/money-price/money-price-interactive.tsx +40 -20
  99. package/src/main/money-price/money-price-types.ts +21 -14
  100. package/src/main/money-price/server.ts +2 -0
  101. package/src/main/pill-select/x-pill-select.tsx +2 -2
  102. package/src/main/server.ts +3 -1
  103. package/src/styles/third-ui.css +8 -0
@@ -10,6 +10,9 @@ import { getActiveProviderConfigUtil, getProductPricing } from './money-price-co
10
10
  import { UserState } from './money-price-types.mjs';
11
11
  import { redirectToCustomerPortal } from './customer-portal.mjs';
12
12
  import { themeButtonGradientClass, themeButtonGradientHoverClass, themeIconColor } from '@windrun-huaiin/base-ui/lib';
13
+ import { AnimeBeamFrame } from '../anime/anime-beam-frame.mjs';
14
+ import 'animejs';
15
+ import '../anime/anime-404-page.mjs';
13
16
 
14
17
  const PLAN_KEYS = ['F1', 'P2', 'U3'];
15
18
  function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPortalApiEndpoint, enableClerkModal = false, enabledBillingTypes, enableSubscriptionUpgrade = true, initialBillingType, disableAutoDetectBilling = false, initUserContext, isInitLoading = false, }) {
@@ -19,11 +22,11 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
19
22
  const providerConfig = useMemo(() => getActiveProviderConfigUtil(config), [config]);
20
23
  const billingOptions = useMemo(() => {
21
24
  const options = data.billingSwitch.options;
22
- // 如果配置了 enabledBillingTypes,只显示配置的类型
25
+ // If enabledBillingTypes is configured, show only those billing types.
23
26
  if (enabledBillingTypes === null || enabledBillingTypes === void 0 ? void 0 : enabledBillingTypes.length) {
24
27
  return options.filter(option => enabledBillingTypes.includes(option.key));
25
28
  }
26
- // 否则显示所有配置的选项
29
+ // Otherwise show all configured options.
27
30
  return options;
28
31
  }, [data.billingSwitch.options, enabledBillingTypes]);
29
32
  const billingOptionMap = useMemo(() => {
@@ -35,11 +38,11 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
35
38
  const defaultBilling = useMemo(() => {
36
39
  var _a;
37
40
  const defaultKey = data.billingSwitch.defaultKey;
38
- // 如果默认值在可用选项中,使用默认值
41
+ // Use the default value when it is available.
39
42
  if (billingOptions.some(opt => opt.key === defaultKey)) {
40
43
  return defaultKey;
41
44
  }
42
- // 否则使用第一个可用选项
45
+ // Otherwise use the first available option.
43
46
  return ((_a = billingOptions[0]) === null || _a === void 0 ? void 0 : _a.key) || 'monthly';
44
47
  }, [data.billingSwitch.defaultKey, billingOptions]);
45
48
  const resolvedInitialBilling = useMemo(() => {
@@ -51,18 +54,18 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
51
54
  }, [initialBillingType, billingOptions, defaultBilling]);
52
55
  const priceIdsByCycle = useMemo(() => {
53
56
  const priceIds = {};
54
- // 为每个可用的计费类型创建价格ID数组
57
+ // Build a price ID list for each available billing type.
55
58
  billingOptions.forEach(option => {
56
59
  priceIds[option.key] = [];
57
60
  if (option.key === 'onetime') {
58
- // 处理积分包产品
61
+ // Handle credit pack products.
59
62
  const creditPacks = providerConfig.creditPackProducts || {};
60
63
  Object.values(creditPacks).forEach((pack) => {
61
64
  priceIds[option.key].push(pack.priceId);
62
65
  });
63
66
  }
64
67
  else {
65
- // 处理订阅产品
68
+ // Handle subscription products.
66
69
  const products = providerConfig.subscriptionProducts || providerConfig.products || {};
67
70
  PLAN_KEYS.forEach(planKey => {
68
71
  const product = products[planKey];
@@ -261,7 +264,7 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
261
264
  userContext,
262
265
  enableSubscriptionUpgrade
263
266
  ]);
264
- // 根据当前计费类型动态选择要显示的 plans
267
+ // Select visible plans dynamically based on the current billing type.
265
268
  const currentPlans = useMemo(() => {
266
269
  if (billingType === 'onetime') {
267
270
  return data.creditsPlans || [];
@@ -286,11 +289,11 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
286
289
  const discountBadgeText = useMemo(() => {
287
290
  if (!(selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.discountText))
288
291
  return null;
289
- // 对于 onetime 模式,直接显示 discountText,不依赖 discountPercent
292
+ // In one-time mode, show discountText directly without relying on discountPercent.
290
293
  if (billingType === 'onetime') {
291
294
  return selectedBillingOption.discountText;
292
295
  }
293
- // 对于订阅模式,查找 discountPercent 并替换
296
+ // In subscription mode, find discountPercent and interpolate it.
294
297
  let discountPercent = null;
295
298
  const products = providerConfig.subscriptionProducts || providerConfig.products || {};
296
299
  PLAN_KEYS.forEach(planKey => {
@@ -304,7 +307,7 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
304
307
  return null;
305
308
  return selectedBillingOption.discountText.replace('{percent}', String(discountPercent));
306
309
  }, [selectedBillingOption, providerConfig, billingType]);
307
- // 配置移动端BillingTypeButton悬浮样式
310
+ // Configure the mobile floating style for BillingTypeButton.
308
311
  return (jsxs(Fragment, { children: [jsx("div", { className: "flex justify-center mb-6 max-md:sticky max-md:top-30 max-md:z-30 max-md:py-2 max-md:bg-transparent", children: jsx("div", { className: "inline-flex bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-full px-2 py-2 sm:px-3 sm:py-3 max-md:w-full max-md:max-w-[340px] max-md:mx-auto shadow-sm", "data-billing-switch": true, children: billingOptions.map(option => {
309
312
  const isActive = option.key === billingType;
310
313
  const buttonClasses = isActive
@@ -313,6 +316,7 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
313
316
  const showBadge = option.key === billingType && !!discountBadgeText;
314
317
  return (jsxs("div", { className: "relative flex items-center justify-center mx-1", children: [showBadge && (jsx("span", { className: "absolute z-10 left-1/2 -translate-x-1/2 -top-3 sm:-top-4 translate-y-[-50%] px-3 py-0.5 text-[0.625rem] sm:text-xs rounded-md bg-yellow-100 text-yellow-800 font-semibold shadow-sm whitespace-nowrap", children: discountBadgeText })), jsx("button", { className: cn('text-sm md:text-base font-medium transition relative text-center z-10 px-2 sm:px-4 py-2 min-w-[100px] sm:min-w-[120px]', buttonClasses), type: "button", "data-billing-button": option.key, onClick: () => setUserSelectedBillingType(option.key), children: option.name })] }, option.key));
315
318
  }) }) }), jsx("div", { className: "w-full", children: jsx("div", { className: "flex flex-wrap justify-center gap-5 md:gap-6 xl:gap-8 w-full max-w-6xl mx-auto", children: currentPlans.map((plan) => {
319
+ var _a, _b;
316
320
  const planKey = plan.key;
317
321
  if (!PLAN_KEYS.includes(planKey)) {
318
322
  console.warn(`Unknown plan key "${plan.key}" detected in pricing plans`);
@@ -321,13 +325,21 @@ function MoneyPriceInteractive({ data, config, checkoutApiEndpoint, customerPort
321
325
  const pricing = getPricingForPlan(planKey);
322
326
  const showBillingSubtitle = plan.showBillingSubTitle !== false;
323
327
  const hasDiscount = !!pricing.discountPercent && !!pricing.originalAmount;
324
- // 移动端宽度样式
325
- return (jsxs("div", { "data-price-plan": planKey, className: cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-5 md:p-8 h-full shadow-sm dark:shadow-none w-[85vw] max-w-[360px]', 'md:w-[clamp(280px,32vw,360px)] md:max-w-[360px] md:shrink-0', 'hover:border-2 hover:border-current', 'focus-within:border-2 focus-within:border-current', themeIconColor), style: { minHeight: maxFeaturesCount * (isTouchDevice ? 86 : 100) }, children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("span", { className: "text-lg md:text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), jsxs("div", { className: "flex flex-col items-start w-full", "data-price-container": planKey, children: [jsxs("div", { className: "flex items-end gap-2", children: [jsx("span", { className: "text-3xl md:text-4xl font-extrabold text-gray-900 dark:text-gray-100", "data-price-value": planKey, children: pricing.amount === 0 ? 'Free' : `${data.currency}${pricing.amount}` }), pricing.amount > 0 && (jsx("span", { className: "text-base md:text-lg text-gray-700 dark:text-gray-300 font-medium mb-1", "data-price-unit": planKey, children: (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.unit) || '/month' }))] }), jsxs("div", { className: "flex flex-col md:flex-row items-start md:items-center gap-1 md:gap-2 min-h-[28px] mt-1", children: [hasDiscount && (jsxs(Fragment, { children: [jsxs("span", { className: "text-sm md:text-base text-gray-400 line-through", "data-price-original": planKey, children: [data.currency, pricing.originalAmount] }), (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.discountText) && (jsx("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", "data-price-discount": planKey, children: selectedBillingOption.discountText.replace('{percent}', String(pricing.discountPercent)) }))] })), jsx("div", { className: cn('flex items-center gap-2 text-[11px] md:text-xs', !showBillingSubtitle && 'opacity-0 select-none'), "data-price-subtitle": planKey, children: showBillingSubtitle && billingType === 'onetime' ? (
326
- // OneTime 模式下的特殊处理:普通文本 + 带样式的产品副标题
327
- jsxs(Fragment, { children: [(selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.subTitle) && (jsx("span", { className: "text-[11px] md:text-xs text-gray-700 dark:text-gray-300 font-medium", children: selectedBillingOption.subTitle })), plan.subtitle && (jsxs("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", children: ["+", plan.subtitle] }))] })) : (
328
- // 其他模式下保持原逻辑
329
- showBillingSubtitle && (jsx("span", { className: "text-[11px] md:text-xs text-gray-700 dark:text-gray-300 font-medium", children: (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.subTitle) || '' }))) })] })] }), jsx("ul", { className: "flex-1 mb-6 mt-4 text-xs md:text-sm leading-5", children: getFeatureRows(plan).map((feature, i) => (jsxs("li", { className: "flex items-start gap-2 mb-2 min-h-[24px] md:min-h-[28px]", "data-feature-item": `${planKey}-${i}`, children: [feature ? (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1", children: feature.icon ? jsx("span", { children: feature.icon }) : jsx("span", { className: "font-bold", children: "\u2713" }) })) : (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full mr-1", children: "\u00A0" })), feature && feature.tag && (jsx("span", { className: "px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle", children: feature.tag })), feature ? (jsxs("div", { className: "flex-1 text-gray-800 dark:text-gray-200", children: [jsx("span", { children: feature.description }), feature.tooltip && (jsx("span", { className: "block text-[11px] text-gray-500 dark:text-gray-400 mt-1", children: feature.tooltip }))] })) : (jsx("span", { children: "\u00A0" }))] }, i))) }), jsx("div", { className: "flex-1" }), jsx(MoneyPriceButton, { planKey: planKey, userContext: userContext, billingType: billingType, onAuth: handleAuth, onAction: handleAction, texts: data.buttonTexts, isProcessing: (processingTarget === null || processingTarget === void 0 ? void 0 : processingTarget.plan) === planKey &&
330
- (processingTarget === null || processingTarget === void 0 ? void 0 : processingTarget.billing) === billingType, isAnyProcessing: !!processingTarget, isInitLoading: isInitLoading, enableSubscriptionUpgrade: enableSubscriptionUpgrade })] }, plan.key));
328
+ const hasStrictDiffAnime = Object.prototype.hasOwnProperty.call((_a = plan.strictDiffAnime) !== null && _a !== void 0 ? _a : {}, billingType);
329
+ const animeTone = hasStrictDiffAnime
330
+ ? (_b = plan.strictDiffAnime) === null || _b === void 0 ? void 0 : _b[billingType]
331
+ : plan.animeTone;
332
+ const hasAnimeTone = !!animeTone;
333
+ return (jsx(AnimeBeamFrame, { active: false, interactive: hasAnimeTone, tone: animeTone !== null && animeTone !== void 0 ? animeTone : 'theme', radius: 16, className: cn('h-full w-[85vw] max-w-[360px]', 'md:w-[clamp(280px,32vw,360px)] md:max-w-[360px] md:shrink-0'), children: jsxs("div", { "data-price-plan": planKey, className: cn('flex flex-col bg-white dark:bg-gray-800/60 rounded-2xl border border-gray-300 dark:border-[#7c3aed40] transition p-5 md:p-8 h-full shadow-sm dark:shadow-none', !hasAnimeTone && [
334
+ 'hover:border-current',
335
+ 'focus-within:border-current',
336
+ themeIconColor,
337
+ ]), style: { minHeight: maxFeaturesCount * (isTouchDevice ? 86 : 100) }, children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("span", { className: "text-lg md:text-xl font-bold text-gray-900 dark:text-gray-100", children: plan.title }), plan.titleTags && plan.titleTags.map((tag, i) => (jsx("span", { className: "px-2 py-0.5 text-xs rounded bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200 font-semibold align-middle", children: tag }, i)))] }), jsxs("div", { className: "flex flex-col items-start w-full", "data-price-container": planKey, children: [jsxs("div", { className: "flex items-end gap-2", children: [jsx("span", { className: "text-3xl md:text-4xl font-extrabold text-gray-900 dark:text-gray-100", "data-price-value": planKey, children: pricing.amount === 0 ? 'Free' : `${data.currency}${pricing.amount}` }), pricing.amount > 0 && (jsx("span", { className: "text-base md:text-lg text-gray-700 dark:text-gray-300 font-medium mb-1", "data-price-unit": planKey, children: (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.unit) || '/month' }))] }), jsxs("div", { className: "flex flex-col md:flex-row items-start md:items-center gap-1 md:gap-2 min-h-[28px] mt-1", children: [hasDiscount && (jsxs(Fragment, { children: [jsxs("span", { className: "text-sm md:text-base text-gray-400 line-through", "data-price-original": planKey, children: [data.currency, pricing.originalAmount] }), (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.discountText) && (jsx("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", "data-price-discount": planKey, children: selectedBillingOption.discountText.replace('{percent}', String(pricing.discountPercent)) }))] })), jsx("div", { className: cn('flex items-center gap-2 text-[11px] md:text-xs', !showBillingSubtitle && 'opacity-0 select-none'), "data-price-subtitle": planKey, children: showBillingSubtitle && billingType === 'onetime' ? (
338
+ // Special one-time mode rendering: plain text plus styled product subtitle.
339
+ jsxs(Fragment, { children: [(selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.subTitle) && (jsx("span", { className: "text-[11px] md:text-xs text-gray-700 dark:text-gray-300 font-medium", children: selectedBillingOption.subTitle })), plan.subtitle && (jsxs("span", { className: "px-2 py-0.5 text-[11px] md:text-xs rounded bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 font-semibold align-middle", children: ["+", plan.subtitle] }))] })) : (
340
+ // Keep the original rendering for other modes.
341
+ showBillingSubtitle && (jsx("span", { className: "text-[11px] md:text-xs text-gray-700 dark:text-gray-300 font-medium", children: (selectedBillingOption === null || selectedBillingOption === void 0 ? void 0 : selectedBillingOption.subTitle) || '' }))) })] })] }), jsx("ul", { className: "flex-1 mb-6 mt-4 text-xs md:text-sm leading-5", children: getFeatureRows(plan).map((feature, i) => (jsxs("li", { className: "flex items-start gap-2 mb-2 min-h-[24px] md:min-h-[28px]", "data-feature-item": `${planKey}-${i}`, children: [feature ? (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200 mr-1", children: feature.icon ? jsx("span", { children: feature.icon }) : jsx("span", { className: "font-bold", children: "\u2713" }) })) : (jsx("span", { className: "inline-flex items-center justify-center w-5 h-5 rounded-full mr-1", children: "\u00A0" })), feature && feature.tag && (jsx("span", { className: "px-1 py-0.5 text-[6px] rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 font-semibold align-middle", children: feature.tag })), feature ? (jsxs("div", { className: "flex-1 text-gray-800 dark:text-gray-200", children: [jsx("span", { children: feature.description }), feature.tooltip && (jsx("span", { className: "block text-[11px] text-gray-500 dark:text-gray-400 mt-1", children: feature.tooltip }))] })) : (jsx("span", { children: "\u00A0" }))] }, i))) }), jsx("div", { className: "flex-1" }), jsx(MoneyPriceButton, { planKey: planKey, userContext: userContext, billingType: billingType, onAuth: handleAuth, onAction: handleAction, texts: data.buttonTexts, isProcessing: (processingTarget === null || processingTarget === void 0 ? void 0 : processingTarget.plan) === planKey &&
342
+ (processingTarget === null || processingTarget === void 0 ? void 0 : processingTarget.billing) === billingType, isAnyProcessing: !!processingTarget, isInitLoading: isInitLoading, enableSubscriptionUpgrade: enableSubscriptionUpgrade })] }) }, `${billingType}-${plan.key}-${animeTone !== null && animeTone !== void 0 ? animeTone : 'none'}`));
331
343
  }) }) })] }));
332
344
  }
333
345
 
@@ -1,7 +1,7 @@
1
1
  import type { XCredit, XSubscription, XUser } from '../../clerk/fingerprint/types';
2
2
  /**
3
3
  * Money Price Component Types
4
- * 价格组件类型定义
4
+ * Pricing component type definitions.
5
5
  */
6
6
  export declare enum UserState {
7
7
  Anonymous = "anonymous",
@@ -117,6 +117,8 @@ export interface MoneyPriceButtonProps {
117
117
  isInitLoading: boolean;
118
118
  enableSubscriptionUpgrade?: boolean;
119
119
  }
120
+ export type MoneyPriceAnimeTone = 'theme' | 'rainbow' | 'mono' | 'warm' | 'cool';
121
+ export type MoneyPriceStrictDiffAnime = Record<string, MoneyPriceAnimeTone | null | undefined>;
120
122
  export interface MoneyPriceData {
121
123
  title: string;
122
124
  subtitle: string;
@@ -133,6 +135,8 @@ export interface MoneyPriceData {
133
135
  subscriptionPlans: Array<{
134
136
  key: string;
135
137
  title: string;
138
+ animeTone?: MoneyPriceAnimeTone;
139
+ strictDiffAnime?: MoneyPriceStrictDiffAnime;
136
140
  showBillingSubTitle?: boolean;
137
141
  titleTags?: string[];
138
142
  features?: Array<{
@@ -146,6 +150,8 @@ export interface MoneyPriceData {
146
150
  key: string;
147
151
  title: string;
148
152
  subtitle?: string;
153
+ animeTone?: MoneyPriceAnimeTone;
154
+ strictDiffAnime?: MoneyPriceStrictDiffAnime;
149
155
  showBillingSubTitle?: boolean;
150
156
  titleTags?: string[];
151
157
  features?: Array<{
@@ -2,9 +2,9 @@
2
2
 
3
3
  /**
4
4
  * Money Price Component Types
5
- * 价格组件类型定义
5
+ * Pricing component type definitions.
6
6
  */
7
- // 用户状态枚举
7
+ // User status enum.
8
8
  exports.UserState = void 0;
9
9
  (function (UserState) {
10
10
  UserState["Anonymous"] = "anonymous";
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Money Price Component Types
3
- * 价格组件类型定义
3
+ * Pricing component type definitions.
4
4
  */
5
- // 用户状态枚举
5
+ // User status enum.
6
6
  var UserState;
7
7
  (function (UserState) {
8
8
  UserState["Anonymous"] = "anonymous";
@@ -1,5 +1,5 @@
1
1
  export { MoneyPrice } from './money-price';
2
2
  export { getActiveProviderConfigUtil, getCreditsFromPriceIdUtil, getPriceConfigUtil, } from './money-price-config-util';
3
3
  export { buildMoneyPriceData } from './money-price-data';
4
- export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext, } from './money-price-types';
4
+ export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, MoneyPriceAnimeTone, MoneyPriceStrictDiffAnime, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext, } from './money-price-types';
5
5
  export { UserState } from './money-price-types';
@@ -26,7 +26,7 @@ function XPillSelect(props) {
26
26
  const isAllSelected = props.mode === 'multiple' &&
27
27
  allOptionValues.length > 0 &&
28
28
  allOptionValues.every((value) => selectedValues.includes(value));
29
- const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || '全部' : null;
29
+ const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || 'All' : null;
30
30
  const hasVisiblePillLimit = props.mode === 'multiple' && typeof maxVisiblePills === 'number' && maxVisiblePills >= 0;
31
31
  const visibleSelectedValues = aggregatedSelectedLabel || !hasVisiblePillLimit
32
32
  ? selectedValues
@@ -126,7 +126,7 @@ function XPillSelect(props) {
126
126
  event.stopPropagation();
127
127
  removeValue(selectedValue);
128
128
  }, disabled: disabled, className: utils.cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'gap-1 px-2.5 py-0.5 text-[11px]' : 'gap-1.5 px-3 py-1 text-xs', lib.themeBgColor, lib.themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: optionLabel, children: jsxRuntime.jsx("span", { className: utils.cn('truncate', maxPillWidthClassName), children: optionLabel }) }, selectedValue));
129
- }), hiddenSelectedCount > 0 ? (jsxRuntime.jsxs("span", { className: utils.cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `还有 ${hiddenSelectedCount} 项未展开`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsxRuntime.jsx("span", { className: utils.cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsxRuntime.jsx(icons.ChevronDownIcon, { className: utils.cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: utils.cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && lib.themeBorderColor), children: [inputEnabled ? (jsxRuntime.jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
129
+ }), hiddenSelectedCount > 0 ? (jsxRuntime.jsxs("span", { className: utils.cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `${hiddenSelectedCount} items remain unexpanded`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsxRuntime.jsx("span", { className: utils.cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsxRuntime.jsx(icons.ChevronDownIcon, { className: utils.cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxRuntime.jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: utils.cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && lib.themeBorderColor), children: [inputEnabled ? (jsxRuntime.jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
130
130
  if (event.key !== 'Enter') {
131
131
  return;
132
132
  }
@@ -24,7 +24,7 @@ function XPillSelect(props) {
24
24
  const isAllSelected = props.mode === 'multiple' &&
25
25
  allOptionValues.length > 0 &&
26
26
  allOptionValues.every((value) => selectedValues.includes(value));
27
- const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || '全部' : null;
27
+ const aggregatedSelectedLabel = isAllSelected ? (allSelectedLabel === null || allSelectedLabel === void 0 ? void 0 : allSelectedLabel.trim()) || 'All' : null;
28
28
  const hasVisiblePillLimit = props.mode === 'multiple' && typeof maxVisiblePills === 'number' && maxVisiblePills >= 0;
29
29
  const visibleSelectedValues = aggregatedSelectedLabel || !hasVisiblePillLimit
30
30
  ? selectedValues
@@ -124,7 +124,7 @@ function XPillSelect(props) {
124
124
  event.stopPropagation();
125
125
  removeValue(selectedValue);
126
126
  }, disabled: disabled, className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'gap-1 px-2.5 py-0.5 text-[11px]' : 'gap-1.5 px-3 py-1 text-xs', themeBgColor, themeIconColor, 'hover:brightness-95 dark:hover:brightness-110', disabled && 'cursor-not-allowed opacity-60'), title: optionLabel, children: jsx("span", { className: cn('truncate', maxPillWidthClassName), children: optionLabel }) }, selectedValue));
127
- }), hiddenSelectedCount > 0 ? (jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `还有 ${hiddenSelectedCount} 项未展开`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsx("span", { className: cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsx(ChevronDownIcon, { className: cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && themeBorderColor), children: [inputEnabled ? (jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
127
+ }), hiddenSelectedCount > 0 ? (jsxs("span", { className: cn('inline-flex max-w-full items-center rounded-full font-semibold transition', compact ? 'px-2.5 py-0.5 text-[11px]' : 'px-3 py-1 text-xs', 'bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:text-white'), title: `${hiddenSelectedCount} items remain unexpanded`, children: ["+", hiddenSelectedCount] })) : null] }))) : (jsx("span", { className: cn(compact ? 'text-xs' : 'text-sm', 'text-slate-500 dark:text-slate-400'), children: emptyLabel })) }), jsx(ChevronDownIcon, { className: cn(compact ? 'h-3.5 w-3.5' : 'h-4 w-4', 'shrink-0 text-slate-500 transition-transform dark:text-slate-400', open && 'rotate-180') })] }), open ? (jsxs("div", { role: "listbox", "aria-multiselectable": props.mode === 'multiple' ? true : undefined, className: cn('absolute left-0 right-0 top-[calc(100%+0.375rem)] z-50 rounded-3xl border border-black/10 bg-neutral-100 shadow-xl dark:border-white/10 dark:bg-neutral-900', compact ? 'space-y-2.5 p-3' : 'space-y-3 p-4', open && themeBorderColor), children: [inputEnabled ? (jsx("input", { value: draftValue, onChange: (event) => setDraftValue(event.target.value.replaceAll(',', '')), onKeyDown: (event) => {
128
128
  if (event.key !== 'Enter') {
129
129
  return;
130
130
  }
@@ -12,5 +12,5 @@ export type { CreditOverviewData, CreditBucket, CreditBucketStatus, Subscription
12
12
  export { MoneyPrice } from './money-price/money-price';
13
13
  export { getActiveProviderConfigUtil, getCreditsFromPriceIdUtil, getPriceConfigUtil } from './money-price/money-price-config-util';
14
14
  export { buildMoneyPriceData } from './money-price/money-price-data';
15
- export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext } from './money-price/money-price-types';
15
+ export type { MoneyPriceConfig, MoneyPriceProps, MoneyPriceInteractiveProps, MoneyPriceButtonProps, MoneyPriceData, MoneyPriceAnimeTone, MoneyPriceStrictDiffAnime, InitUserContext, PaymentProvider, PaymentProviderConfig, EnhancePricePlan, SubscriptionProductConfig, CreditPackProductConfig, UserContext } from './money-price/money-price-types';
16
16
  export { UserState } from './money-price/money-price-types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "30.1.0",
3
+ "version": "31.0.1",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "exports": {
6
6
  "./clerk": {
@@ -221,9 +221,9 @@
221
221
  "LICENSE"
222
222
  ],
223
223
  "dependencies": {
224
- "@clerk/localizations": "^4.2.2",
225
- "@clerk/nextjs": "^7.0.5",
226
- "@clerk/shared": "^4.3.1",
224
+ "@clerk/localizations": "^4.6.2",
225
+ "@clerk/nextjs": "^7.3.3",
226
+ "@clerk/shared": "^4.10.2",
227
227
  "@fingerprintjs/fingerprintjs": "^5.1.0",
228
228
  "animejs": "^4.4.1",
229
229
  "class-variance-authority": "^0.7.1",
@@ -243,9 +243,9 @@
243
243
  "tslib": "^2.8.1",
244
244
  "unified": "^11.0.5",
245
245
  "zod": "^4.3.6",
246
- "@windrun-huaiin/base-ui": "^30.1.0",
247
- "@windrun-huaiin/lib": "^30.0.0",
248
- "@windrun-huaiin/contracts": "^30.0.0"
246
+ "@windrun-huaiin/base-ui": "^31.0.0",
247
+ "@windrun-huaiin/contracts": "^31.0.0",
248
+ "@windrun-huaiin/lib": "^31.0.0"
249
249
  },
250
250
  "peerDependencies": {
251
251
  "clsx": "^2.1.1",
@@ -281,6 +281,12 @@
281
281
  "publishConfig": {
282
282
  "access": "public"
283
283
  },
284
+ "homepage": "https://d8ger.com",
285
+ "repository": {
286
+ "type": "git",
287
+ "url": "git+https://github.com/caofanCPU/next-ai-build.git",
288
+ "directory": "packages/third-ui"
289
+ },
284
290
  "scripts": {
285
291
  "build": "rollup -c rollup.config.mjs",
286
292
  "build:prod": "rollup -c rollup.config.mjs",
@@ -188,7 +188,7 @@ export function AIPromptTextarea({
188
188
  }
189
189
  }
190
190
 
191
- // 渲染标题组件
191
+ // Render the title component.
192
192
  const renderTitle = () => {
193
193
  if (!title?.trim()) return null
194
194
 
@@ -200,7 +200,7 @@ export function AIPromptTextarea({
200
200
  )
201
201
  }
202
202
 
203
- // 渲染textarea组件
203
+ // Render the textarea component.
204
204
  const renderTextarea = (isEmbedded = false) => (
205
205
  <textarea
206
206
  ref={textareaRef}
@@ -220,7 +220,7 @@ export function AIPromptTextarea({
220
220
  />
221
221
  )
222
222
 
223
- // 渲染单词计数
223
+ // Render the word count.
224
224
  const renderWordCount = () => {
225
225
  if (!showWordCount) return null
226
226
 
@@ -238,7 +238,7 @@ export function AIPromptTextarea({
238
238
  )
239
239
  }
240
240
 
241
- // 如果有标题且需要嵌入,则渲染内部标题布局
241
+ // Render the embedded title layout when a title is present and embedded mode is enabled.
242
242
  if (embed && (title)) {
243
243
  return (
244
244
  <div className="space-y-2">
@@ -256,7 +256,7 @@ export function AIPromptTextarea({
256
256
  )
257
257
  }
258
258
 
259
- // 默认布局:外部标题或无标题
259
+ // Default layout: external title or no title.
260
260
  return (
261
261
  <div className="space-y-2">
262
262
  {renderTitle()}
@@ -264,4 +264,4 @@ export function AIPromptTextarea({
264
264
  {renderWordCount()}
265
265
  </div>
266
266
  )
267
- }
267
+ }
@@ -0,0 +1,16 @@
1
+ export const clerkAuthModalAppearance = {
2
+ elements: {
3
+ modalContent: '!items-start !pt-16 sm:!pt-24',
4
+ cardBox: '!mt-0',
5
+ },
6
+ };
7
+
8
+ export const clerkAuthPageAppearance = {
9
+ elements: {
10
+ rootBox: 'w-full',
11
+ cardBox: 'mx-auto',
12
+ },
13
+ };
14
+
15
+ export const clerkAuthPageContainerClassName =
16
+ 'flex min-h-[calc(100dvh-var(--fd-banner-height,0px)-var(--fd-header-height,3.5rem))] w-full items-start justify-center px-6 pt-[calc(var(--fd-header-height,3.5rem)+2rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-header-height,3.5rem)+4.5rem)] md:pb-8';
@@ -7,9 +7,7 @@
7
7
 
8
8
  import { SignUpWithFingerprint } from './signup-with-fingerprint-client';
9
9
  import { SignInWithFingerprint } from './signin-with-fingerprint-client';
10
-
11
- const clerkPageContainerClassName =
12
- 'flex min-h-dvh w-full items-start justify-center px-6 pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1.5rem)] md:pb-8';
10
+ import { clerkAuthPageContainerClassName } from './clerk-auth-appearance';
13
11
 
14
12
  /**
15
13
  * Create a SignUp page with fingerprint support
@@ -18,7 +16,7 @@ const clerkPageContainerClassName =
18
16
  export function createSignUpPageWithFingerprint() {
19
17
  return function SignUpPage() {
20
18
  return (
21
- <div className={clerkPageContainerClassName}>
19
+ <div className={clerkAuthPageContainerClassName}>
22
20
  <SignUpWithFingerprint />
23
21
  </div>
24
22
  );
@@ -32,7 +30,7 @@ export function createSignUpPageWithFingerprint() {
32
30
  export function createSignInPageWithFingerprint() {
33
31
  return function SignInPage() {
34
32
  return (
35
- <div className={clerkPageContainerClassName}>
33
+ <div className={clerkAuthPageContainerClassName}>
36
34
  <SignInWithFingerprint />
37
35
  </div>
38
36
  );
@@ -1,14 +1,15 @@
1
1
  import { SignIn, SignUp, Waitlist } from '@clerk/nextjs';
2
-
3
- const clerkPageContainerClassName =
4
- 'flex min-h-dvh w-full items-start justify-center px-6 pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1rem)] pb-6 md:px-8 md:pt-[calc(var(--fd-banner-height,0px)+var(--fd-header-height,3.5rem)+1.5rem)] md:pb-8';
2
+ import {
3
+ clerkAuthPageAppearance,
4
+ clerkAuthPageContainerClassName,
5
+ } from './clerk-auth-appearance';
5
6
 
6
7
  // Legacy page generators (for backward compatibility)
7
8
  export function createSignInPage() {
8
9
  return function SignInPage() {
9
10
  return (
10
- <div className={clerkPageContainerClassName}>
11
- <SignIn />
11
+ <div className={clerkAuthPageContainerClassName}>
12
+ <SignIn appearance={clerkAuthPageAppearance} />
12
13
  </div>
13
14
  );
14
15
  };
@@ -17,8 +18,8 @@ export function createSignInPage() {
17
18
  export function createSignUpPage() {
18
19
  return function SignUpPage() {
19
20
  return (
20
- <div className={clerkPageContainerClassName}>
21
- <SignUp />
21
+ <div className={clerkAuthPageContainerClassName}>
22
+ <SignUp appearance={clerkAuthPageAppearance} />
22
23
  </div>
23
24
  );
24
25
  };
@@ -27,7 +28,7 @@ export function createSignUpPage() {
27
28
  export function createWaitlistPage() {
28
29
  return function WaitlistPage() {
29
30
  return (
30
- <div className={clerkPageContainerClassName}>
31
+ <div className={clerkAuthPageContainerClassName}>
31
32
  <Waitlist />
32
33
  </div>
33
34
  );
@@ -4,6 +4,7 @@ import { useState } from 'react';
4
4
  import { ClerkLoaded, ClerkLoading, SignInButton, UserButton, useAuth } from "@clerk/nextjs";
5
5
  import { ReceiptTextIcon, ShieldUserIcon } from '@windrun-huaiin/base-ui/icons';
6
6
  import { themeButtonGradientClass } from '@windrun-huaiin/base-ui/lib';
7
+ import { clerkAuthModalAppearance } from './clerk-auth-appearance';
7
8
  import { SignUpButtonWithFingerprint } from './signup-button-with-fingerprint-client';
8
9
 
9
10
  interface ClerkUserData {
@@ -51,11 +52,19 @@ export function ClerkUserClient({ data }: { data: ClerkUserData }) {
51
52
  )}
52
53
  </div>
53
54
  )}
54
- <SignInButton mode={data.clerkAuthInModal ? 'modal' : 'redirect'}>
55
- <button className="w-16 sm:w-20 h-8 sm:h-9 px-1.5 sm:px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-xs sm:text-sm whitespace-nowrap">
56
- {data.signIn}
57
- </button>
58
- </SignInButton>
55
+ {data.clerkAuthInModal ? (
56
+ <SignInButton mode="modal" appearance={clerkAuthModalAppearance}>
57
+ <button className="w-16 sm:w-20 h-8 sm:h-9 px-1.5 sm:px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-xs sm:text-sm whitespace-nowrap">
58
+ {data.signIn}
59
+ </button>
60
+ </SignInButton>
61
+ ) : (
62
+ <SignInButton mode="redirect">
63
+ <button className="w-16 sm:w-20 h-8 sm:h-9 px-1.5 sm:px-2 border border-gray-300 rounded-full hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 text-center text-xs sm:text-sm whitespace-nowrap">
64
+ {data.signIn}
65
+ </button>
66
+ </SignInButton>
67
+ )}
59
68
  </>
60
69
  )}
61
70
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Fingerprint Client Utilities
3
- * 客户端专用的指纹生成和管理逻辑
4
- * 只能在浏览器环境中使用
3
+ * Client-only fingerprint generation and management logic.
4
+ * Must be used only in browser environments.
5
5
  */
6
6
 
7
7
  import FingerprintJS from '@fingerprintjs/fingerprintjs';
@@ -41,24 +41,24 @@ type FirstTouchData = {
41
41
  };
42
42
 
43
43
  /**
44
- * 检查浏览器存储(localStorage cookie)中的指纹 ID
45
- * 返回有效的 ID null
44
+ * Check fingerprint ID in browser storage, including localStorage and cookies.
45
+ * Returns a valid ID or null.
46
46
  */
47
47
  function checkStoredFingerprintId(): string | null {
48
48
  if (typeof window === 'undefined') {
49
49
  return null;
50
50
  }
51
51
 
52
- // 优先检查 localStorage
52
+ // Prefer localStorage.
53
53
  const localStorageId = getLocalStorageValue(FINGERPRINT_STORAGE_KEY);
54
54
  if (localStorageId && isValidFingerprintId(localStorageId)) {
55
55
  return localStorageId;
56
56
  }
57
57
 
58
- // 检查 cookie
58
+ // Check cookies.
59
59
  const cookieId = getCookieValue(FINGERPRINT_COOKIE_NAME);
60
60
  if (cookieId && isValidFingerprintId(cookieId)) {
61
- // 同步到 localStorage
61
+ // Sync back to localStorage.
62
62
  setLocalStorageValue(FINGERPRINT_STORAGE_KEY, cookieId);
63
63
  return cookieId;
64
64
  }
@@ -203,34 +203,34 @@ export function getOrCreateFirstTouchData(): FirstTouchData | null {
203
203
  }
204
204
 
205
205
  /**
206
- * 生成基于真实浏览器特征的fingerprint ID
207
- * 使用 FingerprintJS 收集浏览器特征并生成唯一标识
206
+ * Generate a fingerprint ID from real browser characteristics.
207
+ * Uses FingerprintJS to collect browser signals and create a stable identifier.
208
208
  */
209
209
  export async function generateFingerprintId(): Promise<string> {
210
210
  if (typeof window === 'undefined') {
211
211
  throw new Error('generateFingerprintId can only be used in browser environment');
212
212
  }
213
213
 
214
- // 检查现有 ID
214
+ // Check for an existing ID.
215
215
  const existingId = checkStoredFingerprintId();
216
216
  if (existingId) {
217
217
  return existingId;
218
218
  }
219
219
 
220
220
  try {
221
- // 使用 FingerprintJS 生成指纹
221
+ // Generate a fingerprint with FingerprintJS.
222
222
  const fp = await FingerprintJS.load();
223
223
  const result = await fp.get();
224
224
  const fingerprintId = `fp_${result.visitorId}`;
225
225
 
226
- // 存储到 localStorage cookie
226
+ // Store in localStorage and cookies.
227
227
  setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fingerprintId);
228
228
  setCookie(FINGERPRINT_COOKIE_NAME, fingerprintId, 365);
229
229
 
230
230
  return fingerprintId;
231
231
  } catch (error) {
232
232
  console.warn('Failed to generate fingerprint with FingerprintJS:', error);
233
- // 降级方案:生成基于时间戳和随机数的 ID
233
+ // Fallback: generate an ID from timestamp and randomness.
234
234
  const fallbackId = `fp_fallback_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
235
235
  setLocalStorageValue(FINGERPRINT_STORAGE_KEY, fallbackId);
236
236
  setCookie(FINGERPRINT_COOKIE_NAME, fallbackId, 365);
@@ -240,14 +240,14 @@ export async function generateFingerprintId(): Promise<string> {
240
240
  }
241
241
 
242
242
  /**
243
- * 获取当前的fingerprint ID
243
+ * Get the current fingerprint ID.
244
244
  */
245
245
  export function getFingerprintId(): string | null {
246
246
  return checkStoredFingerprintId();
247
247
  }
248
248
 
249
249
  /**
250
- * 设置fingerprint ID到存储
250
+ * Store a fingerprint ID.
251
251
  */
252
252
  export function setFingerprintId(fingerprintId: string): void {
253
253
  if (typeof window === 'undefined') {
@@ -263,7 +263,7 @@ export function setFingerprintId(fingerprintId: string): void {
263
263
  }
264
264
 
265
265
  /**
266
- * 清除fingerprint ID
266
+ * Clear the fingerprint ID.
267
267
  */
268
268
  export function clearFingerprintId(): void {
269
269
  if (typeof window === 'undefined') {
@@ -275,8 +275,8 @@ export function clearFingerprintId(): void {
275
275
  }
276
276
 
277
277
  /**
278
- * 获取或生成fingerprint ID
279
- * 如果不存在则自动生成新的
278
+ * Get or generate a fingerprint ID.
279
+ * Automatically creates one when none exists.
280
280
  */
281
281
  export async function getOrGenerateFingerprintId(): Promise<string> {
282
282
  const existingId = checkStoredFingerprintId();
@@ -288,7 +288,7 @@ export async function getOrGenerateFingerprintId(): Promise<string> {
288
288
  }
289
289
 
290
290
  /**
291
- * 创建包含fingerprint ID的fetch headers
291
+ * Create fetch headers containing the fingerprint ID.
292
292
  */
293
293
  export async function createFingerprintHeaders(): Promise<Record<string, string>> {
294
294
  const fingerprintId = await getOrGenerateFingerprintId();
@@ -330,7 +330,7 @@ export function createFingerprintFetch() {
330
330
  };
331
331
  }
332
332
 
333
- // Cookie 辅助函数 (私有)
333
+ // Private cookie helpers.
334
334
  function getCookieValue(name: string): string | null {
335
335
  if (typeof document === 'undefined') {
336
336
  return null;