@tomako/tools-runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/package.json +629 -0
- package/src/assets/icons/brands/social/bluesky-icon.tsx +9 -0
- package/src/assets/icons/brands/social/facebook-icon.tsx +9 -0
- package/src/assets/icons/brands/social/index.ts +8 -0
- package/src/assets/icons/brands/social/instagram-icon.tsx +9 -0
- package/src/assets/icons/brands/social/linkedin-icon.tsx +9 -0
- package/src/assets/icons/brands/social/threads-icon.tsx +9 -0
- package/src/assets/icons/brands/social/tiktok-icon.tsx +10 -0
- package/src/assets/icons/brands/social/x-icon.tsx +9 -0
- package/src/assets/icons/brands/social/youtube-icon.tsx +9 -0
- package/src/assets/icons/social-profile/facebook-icon.tsx +12 -0
- package/src/assets/icons/social-profile/index.ts +29 -0
- package/src/assets/icons/social-profile/instagram-icon.tsx +18 -0
- package/src/assets/icons/social-profile/linkedin-icon.tsx +12 -0
- package/src/assets/icons/social-profile/tiktok-icon.tsx +12 -0
- package/src/assets/icons/social-profile/x-icon.tsx +13 -0
- package/src/assets/icons/social-profile/youtube-icon.tsx +12 -0
- package/src/components/tools/engine/form-builder.tsx +219 -0
- package/src/components/tools/engine/package-widget-loader.tsx +38 -0
- package/src/components/tools/engine/package-widget-runtime.tsx +409 -0
- package/src/components/tools/engine/package-widget.tsx +77 -0
- package/src/components/tools/engine/result-view.tsx +596 -0
- package/src/components/tools/engine/use-legacy-llm-task.ts +64 -0
- package/src/components/tools/engine/use-tool-task.ts +195 -0
- package/src/components/tools/market-forecast-report.tsx +1150 -0
- package/src/components/tools/registry.ts +13 -0
- package/src/components/tools/workspace/index.ts +39 -0
- package/src/components/tools/workspace/tool-action-bar.tsx +99 -0
- package/src/components/tools/workspace/tool-empty-panel.tsx +32 -0
- package/src/components/tools/workspace/tool-error-panel.tsx +40 -0
- package/src/components/tools/workspace/tool-field.tsx +124 -0
- package/src/components/tools/workspace/tool-file-upload.tsx +112 -0
- package/src/components/tools/workspace/tool-form-section.tsx +21 -0
- package/src/components/tools/workspace/tool-form-shell.tsx +36 -0
- package/src/components/tools/workspace/tool-loading-panel.tsx +269 -0
- package/src/components/tools/workspace/tool-option-row.tsx +97 -0
- package/src/components/tools/workspace/tool-preview-panel.tsx +31 -0
- package/src/components/tools/workspace/tool-result-actions.tsx +67 -0
- package/src/components/tools/workspace/tool-result-file-actions.ts +31 -0
- package/src/components/tools/workspace/tool-result-header.tsx +39 -0
- package/src/components/tools/workspace/tool-status.tsx +13 -0
- package/src/components/tools/workspace/tool-workspace-layout.tsx +226 -0
- package/src/constants/LKS.ts +29 -0
- package/src/constants/related-tools.ts +18 -0
- package/src/constants/social-profile-kit.ts +284 -0
- package/src/constants/tool-assets.ts +7 -0
- package/src/env.d.ts +3 -0
- package/src/features/tools/_templates/standard-tool.container.tsx +90 -0
- package/src/features/tools/app-icon-resizer/app-icon-resizer.container.tsx +11 -0
- package/src/features/tools/app-icon-resizer/app-icon-resizer.spec.ts +15 -0
- package/src/features/tools/app-icon-resizer/widget/app-icon-resizer.tsx +444 -0
- package/src/features/tools/app-icon-resizer/widget/constants.ts +19 -0
- package/src/features/tools/app-icon-resizer/widget/helpers.ts +314 -0
- package/src/features/tools/app-icon-resizer/widget/index.ts +7 -0
- package/src/features/tools/app-icon-resizer/widget/platform-icons.tsx +59 -0
- package/src/features/tools/app-icon-resizer/widget/preview-dialogs.tsx +139 -0
- package/src/features/tools/app-icon-resizer/widget/result-shell.tsx +165 -0
- package/src/features/tools/app-icon-resizer/widget/types.ts +36 -0
- package/src/features/tools/app-store-screenshot-generator/app-store-screenshot-generator.container.tsx +108 -0
- package/src/features/tools/app-store-screenshot-generator/app-store-screenshot-generator.spec.ts +14 -0
- package/src/features/tools/app-store-screenshot-generator/config.ts +3 -0
- package/src/features/tools/app-store-screenshot-generator/widget/app-store-screenshot-generator.tsx +1250 -0
- package/src/features/tools/app-store-screenshot-generator/widget/asset-slots.ts +76 -0
- package/src/features/tools/app-store-screenshot-generator/widget/constants.ts +43 -0
- package/src/features/tools/app-store-screenshot-generator/widget/form-controls.tsx +214 -0
- package/src/features/tools/app-store-screenshot-generator/widget/generation.ts +528 -0
- package/src/features/tools/app-store-screenshot-generator/widget/index.ts +7 -0
- package/src/features/tools/app-store-screenshot-generator/widget/input-media.ts +175 -0
- package/src/features/tools/app-store-screenshot-generator/widget/types.ts +60 -0
- package/src/features/tools/app-store-screenshot-generator/widget/zip.ts +119 -0
- package/src/features/tools/build-in-public-story-generator/build-in-public-story-generator.container.tsx +135 -0
- package/src/features/tools/build-in-public-story-generator/build-in-public-story-generator.spec.ts +15 -0
- package/src/features/tools/build-in-public-story-generator/widget/build-in-public-story-generator.tsx +314 -0
- package/src/features/tools/build-in-public-story-generator/widget/constants.ts +3 -0
- package/src/features/tools/build-in-public-story-generator/widget/helpers.ts +25 -0
- package/src/features/tools/build-in-public-story-generator/widget/index.ts +7 -0
- package/src/features/tools/build-in-public-story-generator/widget/preview-panels.tsx +58 -0
- package/src/features/tools/build-in-public-story-generator/widget/result-shell.tsx +164 -0
- package/src/features/tools/build-in-public-story-generator/widget/types.ts +6 -0
- package/src/features/tools/cold-start-channel-selector/cold-start-channel-selector.container.tsx +30 -0
- package/src/features/tools/cold-start-channel-selector/cold-start-channel-selector.spec.ts +15 -0
- package/src/features/tools/cold-start-channel-selector/widget/cold-start-channel-selector.tsx +417 -0
- package/src/features/tools/cold-start-channel-selector/widget/constants.ts +46 -0
- package/src/features/tools/cold-start-channel-selector/widget/form-controls.tsx +41 -0
- package/src/features/tools/cold-start-channel-selector/widget/helpers.ts +75 -0
- package/src/features/tools/cold-start-channel-selector/widget/index.ts +7 -0
- package/src/features/tools/cold-start-channel-selector/widget/preview-panels.tsx +250 -0
- package/src/features/tools/cold-start-channel-selector/widget/result-shell.tsx +199 -0
- package/src/features/tools/cold-start-channel-selector/widget/types.ts +21 -0
- package/src/features/tools/commercial-bp-generator/commercial-bp-generator.container.tsx +102 -0
- package/src/features/tools/commercial-bp-generator/commercial-bp-generator.spec.ts +14 -0
- package/src/features/tools/commercial-bp-generator/widget/commercial-bp-generator.tsx +676 -0
- package/src/features/tools/commercial-bp-generator/widget/constants.ts +32 -0
- package/src/features/tools/commercial-bp-generator/widget/helpers.ts +147 -0
- package/src/features/tools/commercial-bp-generator/widget/index.ts +7 -0
- package/src/features/tools/commercial-bp-generator/widget/result-shell.tsx +271 -0
- package/src/features/tools/commercial-bp-generator/widget/style-option.tsx +54 -0
- package/src/features/tools/commercial-bp-generator/widget/types.ts +33 -0
- package/src/features/tools/community-feedback-inbox/community-feedback-inbox.container.tsx +126 -0
- package/src/features/tools/community-feedback-inbox/community-feedback-inbox.spec.ts +15 -0
- package/src/features/tools/community-feedback-inbox/widget/community-feedback-inbox.tsx +271 -0
- package/src/features/tools/community-feedback-inbox/widget/constants.ts +3 -0
- package/src/features/tools/community-feedback-inbox/widget/helpers.ts +28 -0
- package/src/features/tools/community-feedback-inbox/widget/index.ts +7 -0
- package/src/features/tools/community-feedback-inbox/widget/preview-panels.tsx +92 -0
- package/src/features/tools/community-feedback-inbox/widget/result-shell.tsx +266 -0
- package/src/features/tools/community-feedback-inbox/widget/types.ts +6 -0
- package/src/features/tools/community-pain-point-extractor/community-pain-point-extractor.container.tsx +124 -0
- package/src/features/tools/community-pain-point-extractor/community-pain-point-extractor.spec.ts +15 -0
- package/src/features/tools/community-pain-point-extractor/widget/community-pain-point-extractor.tsx +404 -0
- package/src/features/tools/community-pain-point-extractor/widget/constants.ts +41 -0
- package/src/features/tools/community-pain-point-extractor/widget/helpers.ts +76 -0
- package/src/features/tools/community-pain-point-extractor/widget/index.ts +8 -0
- package/src/features/tools/community-pain-point-extractor/widget/progress-panels.tsx +251 -0
- package/src/features/tools/community-pain-point-extractor/widget/result-shell.tsx +478 -0
- package/src/features/tools/community-pain-point-extractor/widget/types.ts +33 -0
- package/src/features/tools/community-post-risk-checker/community-post-risk-checker.package.ts +792 -0
- package/src/features/tools/competitor-analysis/competitor-analysis.container.tsx +136 -0
- package/src/features/tools/competitor-analysis/competitor-analysis.spec.ts +15 -0
- package/src/features/tools/competitor-analysis/widget/competitor-analysis.tsx +241 -0
- package/src/features/tools/competitor-analysis/widget/constants.ts +9 -0
- package/src/features/tools/competitor-analysis/widget/index.ts +7 -0
- package/src/features/tools/competitor-analysis/widget/preview-panels.tsx +60 -0
- package/src/features/tools/competitor-analysis/widget/result-shell.tsx +418 -0
- package/src/features/tools/competitor-analysis/widget/types.ts +13 -0
- package/src/features/tools/disclaimer-generator/disclaimer-generator.package.ts +339 -0
- package/src/features/tools/email-template-generator/email-template-generator.container.tsx +224 -0
- package/src/features/tools/email-template-generator/email-template-generator.spec.ts +14 -0
- package/src/features/tools/email-template-generator/widget/email-template-generator.tsx +358 -0
- package/src/features/tools/email-template-generator/widget/helpers.ts +39 -0
- package/src/features/tools/email-template-generator/widget/index.ts +7 -0
- package/src/features/tools/email-template-generator/widget/preview-panel.tsx +311 -0
- package/src/features/tools/email-template-generator/widget/result-text-block.tsx +7 -0
- package/src/features/tools/email-template-generator/widget/types.ts +24 -0
- package/src/features/tools/gtm-readiness-checklist/gtm-readiness-checklist.container.tsx +75 -0
- package/src/features/tools/gtm-readiness-checklist/gtm-readiness-checklist.spec.ts +15 -0
- package/src/features/tools/gtm-readiness-checklist/widget/gtm-readiness-checklist.tsx +122 -0
- package/src/features/tools/gtm-readiness-checklist/widget/index.ts +7 -0
- package/src/features/tools/investor-matching-planner/investor-matching-planner.package.ts +869 -0
- package/src/features/tools/koko-codex-assistant/koko-codex-assistant.container.tsx +151 -0
- package/src/features/tools/koko-codex-assistant/koko-codex-assistant.spec.ts +14 -0
- package/src/features/tools/koko-codex-assistant/widget/index.ts +7 -0
- package/src/features/tools/koko-codex-assistant/widget/koko-codex-assistant-download.tsx +192 -0
- package/src/features/tools/logo-generator/logo-generator.container.tsx +112 -0
- package/src/features/tools/logo-generator/logo-generator.spec.ts +15 -0
- package/src/features/tools/logo-generator/widget/index.ts +7 -0
- package/src/features/tools/logo-generator/widget/logo-generator.tsx +1244 -0
- package/src/features/tools/market-forecast-report/market-forecast-report.container.tsx +73 -0
- package/src/features/tools/market-forecast-report/market-forecast-report.spec.ts +15 -0
- package/src/features/tools/million-user-growth-test/million-user-growth-test.container.tsx +123 -0
- package/src/features/tools/million-user-growth-test/million-user-growth-test.spec.ts +14 -0
- package/src/features/tools/million-user-growth-test/widget/index.ts +7 -0
- package/src/features/tools/million-user-growth-test/widget/million-user-growth-test.tsx +433 -0
- package/src/features/tools/module-loader.ts +107 -0
- package/src/features/tools/og-image-generator/og-image-generator.container.tsx +86 -0
- package/src/features/tools/og-image-generator/og-image-generator.spec.ts +14 -0
- package/src/features/tools/og-image-generator/widget/constants.ts +38 -0
- package/src/features/tools/og-image-generator/widget/form-fields.tsx +240 -0
- package/src/features/tools/og-image-generator/widget/index.ts +7 -0
- package/src/features/tools/og-image-generator/widget/og-image-generator.tsx +742 -0
- package/src/features/tools/og-image-generator/widget/preview-panel.tsx +305 -0
- package/src/features/tools/og-image-generator/widget/types.ts +1 -0
- package/src/features/tools/package/direct-models.ts +20 -0
- package/src/features/tools/package/form.ts +38 -0
- package/src/features/tools/package/l10n.ts +22 -0
- package/src/features/tools/package/package-container.tsx +285 -0
- package/src/features/tools/package/package-loaders.ts +22 -0
- package/src/features/tools/package/package-registry.ts +36 -0
- package/src/features/tools/package/result-markdown.ts +142 -0
- package/src/features/tools/package/schema.ts +379 -0
- package/src/features/tools/package/to-tool-module.ts +20 -0
- package/src/features/tools/package/to-tool-spec.ts +52 -0
- package/src/features/tools/package/types.ts +381 -0
- package/src/features/tools/pitch-deck-generator/pitch-deck-generator.container.tsx +175 -0
- package/src/features/tools/pitch-deck-generator/pitch-deck-generator.spec.ts +14 -0
- package/src/features/tools/pitch-deck-generator/pitch-deck-library-data.ts +476 -0
- package/src/features/tools/pitch-deck-generator/pitch-deck-library.tsx +159 -0
- package/src/features/tools/pitch-deck-generator/widget/ceremony-line.tsx +27 -0
- package/src/features/tools/pitch-deck-generator/widget/constants.ts +27 -0
- package/src/features/tools/pitch-deck-generator/widget/index.ts +7 -0
- package/src/features/tools/pitch-deck-generator/widget/pitch-deck-generator.tsx +763 -0
- package/src/features/tools/pitch-deck-generator/widget/result-helpers.ts +102 -0
- package/src/features/tools/pitch-deck-generator/widget/result-shell.tsx +263 -0
- package/src/features/tools/pitch-deck-generator/widget/slide-frame.tsx +106 -0
- package/src/features/tools/pitch-deck-generator/widget/slide-lightbox.tsx +149 -0
- package/src/features/tools/pitch-deck-generator/widget/style-option.tsx +54 -0
- package/src/features/tools/pitch-deck-generator/widget/types.ts +44 -0
- package/src/features/tools/product-agreement-generator/product-agreement-generator.container.tsx +137 -0
- package/src/features/tools/product-agreement-generator/product-agreement-generator.spec.ts +14 -0
- package/src/features/tools/product-agreement-generator/widget/constants.ts +27 -0
- package/src/features/tools/product-agreement-generator/widget/form-controls.tsx +218 -0
- package/src/features/tools/product-agreement-generator/widget/helpers.ts +68 -0
- package/src/features/tools/product-agreement-generator/widget/index.ts +7 -0
- package/src/features/tools/product-agreement-generator/widget/product-agreement-generator.tsx +671 -0
- package/src/features/tools/product-agreement-generator/widget/result-shell.tsx +473 -0
- package/src/features/tools/product-agreement-generator/widget/types.ts +89 -0
- package/src/features/tools/product-hunt-launch-planner/product-hunt-launch-planner.container.tsx +122 -0
- package/src/features/tools/product-hunt-launch-planner/product-hunt-launch-planner.spec.ts +15 -0
- package/src/features/tools/product-hunt-launch-planner/widget/constants.ts +5 -0
- package/src/features/tools/product-hunt-launch-planner/widget/helpers.ts +44 -0
- package/src/features/tools/product-hunt-launch-planner/widget/index.ts +7 -0
- package/src/features/tools/product-hunt-launch-planner/widget/preview-panel.tsx +143 -0
- package/src/features/tools/product-hunt-launch-planner/widget/product-hunt-launch-planner.tsx +504 -0
- package/src/features/tools/product-hunt-launch-planner/widget/result-shell.tsx +176 -0
- package/src/features/tools/product-hunt-launch-planner/widget/types.ts +20 -0
- package/src/features/tools/product-name-generator/product-name-generator.container.tsx +14 -0
- package/src/features/tools/product-name-generator/product-name-generator.spec.ts +15 -0
- package/src/features/tools/product-name-generator/widget/candidate-card.tsx +273 -0
- package/src/features/tools/product-name-generator/widget/constants.ts +21 -0
- package/src/features/tools/product-name-generator/widget/form-controls.tsx +136 -0
- package/src/features/tools/product-name-generator/widget/helpers.ts +41 -0
- package/src/features/tools/product-name-generator/widget/index.ts +7 -0
- package/src/features/tools/product-name-generator/widget/product-name-generator.tsx +776 -0
- package/src/features/tools/product-name-generator/widget/types.ts +36 -0
- package/src/features/tools/product-positioning-analyzer/product-positioning-analyzer.package.ts +801 -0
- package/src/features/tools/product-poster-generator/product-poster-generator.container.tsx +14 -0
- package/src/features/tools/product-poster-generator/product-poster-generator.spec.ts +15 -0
- package/src/features/tools/product-poster-generator/widget/constants.ts +125 -0
- package/src/features/tools/product-poster-generator/widget/form-controls.tsx +204 -0
- package/src/features/tools/product-poster-generator/widget/helpers.ts +155 -0
- package/src/features/tools/product-poster-generator/widget/index.ts +7 -0
- package/src/features/tools/product-poster-generator/widget/product-poster-generator.tsx +663 -0
- package/src/features/tools/product-poster-generator/widget/result-shell.tsx +12 -0
- package/src/features/tools/product-poster-generator/widget/types.ts +83 -0
- package/src/features/tools/product-ui-generator/product-ui-generator.container.tsx +116 -0
- package/src/features/tools/product-ui-generator/product-ui-generator.spec.ts +14 -0
- package/src/features/tools/product-ui-generator/widget/constants.ts +18 -0
- package/src/features/tools/product-ui-generator/widget/helpers.ts +11 -0
- package/src/features/tools/product-ui-generator/widget/index.ts +7 -0
- package/src/features/tools/product-ui-generator/widget/preview-panels.tsx +73 -0
- package/src/features/tools/product-ui-generator/widget/product-ui-generator.tsx +304 -0
- package/src/features/tools/product-ui-generator/widget/result-shell.tsx +118 -0
- package/src/features/tools/product-ui-generator/widget/types.ts +9 -0
- package/src/features/tools/reddit-pain-point-finder/reddit-pain-point-finder.container.tsx +113 -0
- package/src/features/tools/reddit-pain-point-finder/reddit-pain-point-finder.spec.ts +15 -0
- package/src/features/tools/reddit-pain-point-finder/widget/constants.ts +5 -0
- package/src/features/tools/reddit-pain-point-finder/widget/index.ts +7 -0
- package/src/features/tools/reddit-pain-point-finder/widget/preview-panels.tsx +73 -0
- package/src/features/tools/reddit-pain-point-finder/widget/reddit-pain-point-finder.tsx +281 -0
- package/src/features/tools/reddit-pain-point-finder/widget/result-shell.tsx +203 -0
- package/src/features/tools/reddit-pain-point-finder/widget/types.ts +15 -0
- package/src/features/tools/registry.ts +288 -0
- package/src/features/tools/runtime-registry.ts +186 -0
- package/src/features/tools/shared/create-guide-stack-container.tsx +176 -0
- package/src/features/tools/shared/create-standard-landing-container.tsx +163 -0
- package/src/features/tools/shared/tool-content-sections.tsx +659 -0
- package/src/features/tools/shared/tool-cta.tsx +66 -0
- package/src/features/tools/shared/tool-draft-view.tsx +53 -0
- package/src/features/tools/shared/tool-guide-section.tsx +191 -0
- package/src/features/tools/shared/tool-handoff.tsx +38 -0
- package/src/features/tools/shared/tool-mcp-badge.tsx +23 -0
- package/src/features/tools/shared/tool-message-translator.ts +37 -0
- package/src/features/tools/shared/tool-page-shell.tsx +348 -0
- package/src/features/tools/shared/tool-standard-generation-steps.tsx +52 -0
- package/src/features/tools/shared/tool-standard-landing-sections.tsx +447 -0
- package/src/features/tools/shared/tool-ui-registry.ts +237 -0
- package/src/features/tools/shared/tool-widget-slot.tsx +29 -0
- package/src/features/tools/slogan-generator/slogan-generator.package.ts +641 -0
- package/src/features/tools/social-listening-research-report/social-listening-research-report.container.tsx +124 -0
- package/src/features/tools/social-listening-research-report/social-listening-research-report.spec.ts +15 -0
- package/src/features/tools/social-listening-research-report/widget/constants.ts +4 -0
- package/src/features/tools/social-listening-research-report/widget/index.ts +7 -0
- package/src/features/tools/social-listening-research-report/widget/preview-panels.tsx +73 -0
- package/src/features/tools/social-listening-research-report/widget/result-shell.tsx +216 -0
- package/src/features/tools/social-listening-research-report/widget/social-listening-research-report.tsx +303 -0
- package/src/features/tools/social-listening-research-report/widget/types.ts +15 -0
- package/src/features/tools/social-platform-asset-adapter/social-platform-asset-adapter.container.tsx +123 -0
- package/src/features/tools/social-platform-asset-adapter/social-platform-asset-adapter.spec.ts +15 -0
- package/src/features/tools/social-platform-asset-adapter/widget/constants.ts +43 -0
- package/src/features/tools/social-platform-asset-adapter/widget/helpers.ts +249 -0
- package/src/features/tools/social-platform-asset-adapter/widget/index.ts +7 -0
- package/src/features/tools/social-platform-asset-adapter/widget/result-shell.tsx +350 -0
- package/src/features/tools/social-platform-asset-adapter/widget/section-heading.tsx +18 -0
- package/src/features/tools/social-platform-asset-adapter/widget/social-platform-asset-adapter.tsx +641 -0
- package/src/features/tools/social-platform-asset-adapter/widget/types.ts +40 -0
- package/src/features/tools/social-profile-kit/social-profile-kit.container.tsx +177 -0
- package/src/features/tools/social-profile-kit/social-profile-kit.spec.ts +15 -0
- package/src/features/tools/social-profile-kit/widget/context-editor.tsx +171 -0
- package/src/features/tools/social-profile-kit/widget/generation-runtime.ts +281 -0
- package/src/features/tools/social-profile-kit/widget/index.ts +7 -0
- package/src/features/tools/social-profile-kit/widget/input-panel.tsx +294 -0
- package/src/features/tools/social-profile-kit/widget/platform-dock.tsx +115 -0
- package/src/features/tools/social-profile-kit/widget/profile-canvas.tsx +318 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/bluesky-preview.tsx +188 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/facebook-preview.tsx +168 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/index.ts +1 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/instagram-preview.tsx +278 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/linkedin-preview.tsx +358 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/profile-preview.tsx +348 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/shared.tsx +391 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/threads-preview.tsx +148 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/tiktok-preview.tsx +319 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/x-preview.tsx +341 -0
- package/src/features/tools/social-profile-kit/widget/profile-preview/youtube-preview.tsx +297 -0
- package/src/features/tools/social-profile-kit/widget/social-profile-kit-generator.tsx +1059 -0
- package/src/features/tools/social-profile-kit/widget/types.ts +104 -0
- package/src/features/tools/spec-registry.ts +158 -0
- package/src/features/tools/startup-idea-research/startup-idea-research.container.tsx +111 -0
- package/src/features/tools/startup-idea-research/startup-idea-research.spec.ts +15 -0
- package/src/features/tools/startup-idea-research/widget/constants.ts +11 -0
- package/src/features/tools/startup-idea-research/widget/index.ts +7 -0
- package/src/features/tools/startup-idea-research/widget/preview-panels.tsx +67 -0
- package/src/features/tools/startup-idea-research/widget/result-shell.tsx +323 -0
- package/src/features/tools/startup-idea-research/widget/startup-idea-research.tsx +272 -0
- package/src/features/tools/startup-idea-research/widget/types.ts +21 -0
- package/src/features/tools/tarot-reading/tarot-reading.container.tsx +412 -0
- package/src/features/tools/tarot-reading/tarot-reading.spec.ts +15 -0
- package/src/features/tools/tarot-reading/widget/cards.tsx +152 -0
- package/src/features/tools/tarot-reading/widget/constants.ts +69 -0
- package/src/features/tools/tarot-reading/widget/deck-view.tsx +86 -0
- package/src/features/tools/tarot-reading/widget/deck.ts +86 -0
- package/src/features/tools/tarot-reading/widget/index.ts +7 -0
- package/src/features/tools/tarot-reading/widget/landing-view.tsx +209 -0
- package/src/features/tools/tarot-reading/widget/reading-copy.tsx +138 -0
- package/src/features/tools/tarot-reading/widget/reading-view.tsx +189 -0
- package/src/features/tools/tarot-reading/widget/reveal-view.tsx +50 -0
- package/src/features/tools/tarot-reading/widget/ritual-view.tsx +276 -0
- package/src/features/tools/tarot-reading/widget/tarot-reading.tsx +262 -0
- package/src/features/tools/tarot-reading/widget/types.ts +22 -0
- package/src/features/tools/twitter-gif-downloader/twitter-gif-downloader.container.tsx +114 -0
- package/src/features/tools/twitter-gif-downloader/twitter-gif-downloader.spec.ts +14 -0
- package/src/features/tools/twitter-gif-downloader/widget/index.ts +7 -0
- package/src/features/tools/twitter-gif-downloader/widget/twitter-gif-downloader.tsx +351 -0
- package/src/features/tools/types.ts +68 -0
- package/src/features/tools/user-interview-guide-generator/user-interview-guide-generator.package.ts +774 -0
- package/src/features/tools/user-interview-insight-report-generator/user-interview-insight-report-generator.container.tsx +23 -0
- package/src/features/tools/user-interview-insight-report-generator/user-interview-insight-report-generator.spec.ts +14 -0
- package/src/features/tools/user-interview-insight-report-generator/widget/constants.ts +8 -0
- package/src/features/tools/user-interview-insight-report-generator/widget/helpers.ts +4 -0
- package/src/features/tools/user-interview-insight-report-generator/widget/index.ts +7 -0
- package/src/features/tools/user-interview-insight-report-generator/widget/preview-panels.tsx +55 -0
- package/src/features/tools/user-interview-insight-report-generator/widget/result-shell.tsx +122 -0
- package/src/features/tools/user-interview-insight-report-generator/widget/types.ts +16 -0
- package/src/features/tools/user-interview-insight-report-generator/widget/user-interview-insight-report-generator.tsx +293 -0
- package/src/features/tools/user-interview-record-generator/user-interview-record-generator.container.tsx +18 -0
- package/src/features/tools/user-interview-record-generator/user-interview-record-generator.spec.ts +14 -0
- package/src/features/tools/user-interview-record-generator/widget/constants.ts +9 -0
- package/src/features/tools/user-interview-record-generator/widget/helpers.ts +4 -0
- package/src/features/tools/user-interview-record-generator/widget/index.ts +7 -0
- package/src/features/tools/user-interview-record-generator/widget/preview-panels.tsx +48 -0
- package/src/features/tools/user-interview-record-generator/widget/result-shell.tsx +128 -0
- package/src/features/tools/user-interview-record-generator/widget/types.ts +16 -0
- package/src/features/tools/user-interview-record-generator/widget/user-interview-record-generator.tsx +305 -0
- package/src/features/tools/utm-builder/utm-builder.package.ts +846 -0
- package/src/features/tools/widget-registry.ts +73 -0
- package/src/generated/mcp-tools-registry.json +17 -0
- package/src/host/client-proxy.test.ts +33 -0
- package/src/host/client.ts +678 -0
- package/src/host/server.ts +119 -0
- package/src/i18n/client.ts +11 -0
- package/src/i18n/locale-fallback.ts +38 -0
- package/src/i18n/locale.ts +18 -0
- package/src/i18n/messages/en/page/tools.ts +124 -0
- package/src/i18n/messages/en/tools/app-icon-resizer.ts +244 -0
- package/src/i18n/messages/en/tools/app-store-screenshot-generator.ts +145 -0
- package/src/i18n/messages/en/tools/build-in-public-story-generator.ts +229 -0
- package/src/i18n/messages/en/tools/cold-start-channel-selector.ts +423 -0
- package/src/i18n/messages/en/tools/commercial-bp-generator.ts +304 -0
- package/src/i18n/messages/en/tools/community-feedback-inbox.ts +218 -0
- package/src/i18n/messages/en/tools/community-pain-point-extractor.ts +230 -0
- package/src/i18n/messages/en/tools/community-post-risk-checker.ts +209 -0
- package/src/i18n/messages/en/tools/competitor-analysis.ts +197 -0
- package/src/i18n/messages/en/tools/email-template-generator.ts +302 -0
- package/src/i18n/messages/en/tools/engine.ts +69 -0
- package/src/i18n/messages/en/tools/gtm-readiness-checklist.ts +166 -0
- package/src/i18n/messages/en/tools/index.ts +73 -0
- package/src/i18n/messages/en/tools/koko-codex-assistant.ts +171 -0
- package/src/i18n/messages/en/tools/logo-generator.ts +173 -0
- package/src/i18n/messages/en/tools/market-forecast-report.ts +228 -0
- package/src/i18n/messages/en/tools/million-user-growth-test.ts +386 -0
- package/src/i18n/messages/en/tools/og-image-generator.ts +172 -0
- package/src/i18n/messages/en/tools/pitch-deck-generator.ts +358 -0
- package/src/i18n/messages/en/tools/product-agreement-generator.ts +411 -0
- package/src/i18n/messages/en/tools/product-hunt-launch-planner.ts +272 -0
- package/src/i18n/messages/en/tools/product-name-generator.ts +383 -0
- package/src/i18n/messages/en/tools/product-poster-generator.ts +294 -0
- package/src/i18n/messages/en/tools/product-ui-generator.ts +234 -0
- package/src/i18n/messages/en/tools/reddit-pain-point-finder.ts +163 -0
- package/src/i18n/messages/en/tools/shared.ts +24 -0
- package/src/i18n/messages/en/tools/social-listening-research-report.ts +185 -0
- package/src/i18n/messages/en/tools/social-media-profile-generator.ts +22 -0
- package/src/i18n/messages/en/tools/social-platform-asset-adapter.ts +287 -0
- package/src/i18n/messages/en/tools/social-profile-kit.ts +150 -0
- package/src/i18n/messages/en/tools/startup-idea-research.ts +170 -0
- package/src/i18n/messages/en/tools/tarot-reading.ts +230 -0
- package/src/i18n/messages/en/tools/twitter-gif-downloader.ts +145 -0
- package/src/i18n/messages/en/tools/user-interview-insight-report-generator.ts +242 -0
- package/src/i18n/messages/en/tools/user-interview-record-generator.ts +244 -0
- package/src/i18n/messages/en/tools-ui.ts +704 -0
- package/src/i18n/messages/zh/page/tools.ts +124 -0
- package/src/i18n/messages/zh/tools/app-icon-resizer.ts +242 -0
- package/src/i18n/messages/zh/tools/app-store-screenshot-generator.ts +145 -0
- package/src/i18n/messages/zh/tools/build-in-public-story-generator.ts +230 -0
- package/src/i18n/messages/zh/tools/cold-start-channel-selector.ts +422 -0
- package/src/i18n/messages/zh/tools/commercial-bp-generator.ts +302 -0
- package/src/i18n/messages/zh/tools/community-feedback-inbox.ts +217 -0
- package/src/i18n/messages/zh/tools/community-pain-point-extractor.ts +230 -0
- package/src/i18n/messages/zh/tools/community-post-risk-checker.ts +200 -0
- package/src/i18n/messages/zh/tools/competitor-analysis.ts +192 -0
- package/src/i18n/messages/zh/tools/email-template-generator.ts +302 -0
- package/src/i18n/messages/zh/tools/engine.ts +68 -0
- package/src/i18n/messages/zh/tools/gtm-readiness-checklist.ts +166 -0
- package/src/i18n/messages/zh/tools/index.ts +73 -0
- package/src/i18n/messages/zh/tools/koko-codex-assistant.ts +170 -0
- package/src/i18n/messages/zh/tools/logo-generator.ts +172 -0
- package/src/i18n/messages/zh/tools/market-forecast-report.ts +228 -0
- package/src/i18n/messages/zh/tools/million-user-growth-test.ts +385 -0
- package/src/i18n/messages/zh/tools/og-image-generator.ts +173 -0
- package/src/i18n/messages/zh/tools/pitch-deck-generator.ts +358 -0
- package/src/i18n/messages/zh/tools/product-agreement-generator.ts +401 -0
- package/src/i18n/messages/zh/tools/product-hunt-launch-planner.ts +267 -0
- package/src/i18n/messages/zh/tools/product-name-generator.ts +383 -0
- package/src/i18n/messages/zh/tools/product-poster-generator.ts +282 -0
- package/src/i18n/messages/zh/tools/product-ui-generator.ts +234 -0
- package/src/i18n/messages/zh/tools/reddit-pain-point-finder.ts +162 -0
- package/src/i18n/messages/zh/tools/shared.ts +24 -0
- package/src/i18n/messages/zh/tools/social-listening-research-report.ts +181 -0
- package/src/i18n/messages/zh/tools/social-media-profile-generator.ts +22 -0
- package/src/i18n/messages/zh/tools/social-platform-asset-adapter.ts +284 -0
- package/src/i18n/messages/zh/tools/social-profile-kit.ts +150 -0
- package/src/i18n/messages/zh/tools/startup-idea-research.ts +165 -0
- package/src/i18n/messages/zh/tools/tarot-reading.ts +228 -0
- package/src/i18n/messages/zh/tools/twitter-gif-downloader.ts +138 -0
- package/src/i18n/messages/zh/tools/user-interview-insight-report-generator.ts +224 -0
- package/src/i18n/messages/zh/tools/user-interview-record-generator.ts +226 -0
- package/src/i18n/messages/zh/tools-ui.ts +684 -0
- package/src/i18n/messages/zh-tw/page/tools.ts +115 -0
- package/src/i18n/messages/zh-tw/tools/app-icon-resizer.ts +242 -0
- package/src/i18n/messages/zh-tw/tools/app-store-screenshot-generator.ts +145 -0
- package/src/i18n/messages/zh-tw/tools/build-in-public-story-generator.ts +230 -0
- package/src/i18n/messages/zh-tw/tools/cold-start-channel-selector.ts +440 -0
- package/src/i18n/messages/zh-tw/tools/commercial-bp-generator.ts +302 -0
- package/src/i18n/messages/zh-tw/tools/community-feedback-inbox.ts +217 -0
- package/src/i18n/messages/zh-tw/tools/community-pain-point-extractor.ts +230 -0
- package/src/i18n/messages/zh-tw/tools/community-post-risk-checker.ts +200 -0
- package/src/i18n/messages/zh-tw/tools/competitor-analysis.ts +192 -0
- package/src/i18n/messages/zh-tw/tools/email-template-generator.ts +302 -0
- package/src/i18n/messages/zh-tw/tools/engine.ts +68 -0
- package/src/i18n/messages/zh-tw/tools/gtm-readiness-checklist.ts +166 -0
- package/src/i18n/messages/zh-tw/tools/index.ts +73 -0
- package/src/i18n/messages/zh-tw/tools/koko-codex-assistant.ts +170 -0
- package/src/i18n/messages/zh-tw/tools/logo-generator.ts +71 -0
- package/src/i18n/messages/zh-tw/tools/market-forecast-report.ts +228 -0
- package/src/i18n/messages/zh-tw/tools/million-user-growth-test.ts +385 -0
- package/src/i18n/messages/zh-tw/tools/og-image-generator.ts +173 -0
- package/src/i18n/messages/zh-tw/tools/pitch-deck-generator.ts +358 -0
- package/src/i18n/messages/zh-tw/tools/product-agreement-generator.ts +401 -0
- package/src/i18n/messages/zh-tw/tools/product-hunt-launch-planner.ts +267 -0
- package/src/i18n/messages/zh-tw/tools/product-name-generator.ts +383 -0
- package/src/i18n/messages/zh-tw/tools/product-poster-generator.ts +282 -0
- package/src/i18n/messages/zh-tw/tools/product-ui-generator.ts +234 -0
- package/src/i18n/messages/zh-tw/tools/reddit-pain-point-finder.ts +162 -0
- package/src/i18n/messages/zh-tw/tools/shared.ts +24 -0
- package/src/i18n/messages/zh-tw/tools/social-listening-research-report.ts +181 -0
- package/src/i18n/messages/zh-tw/tools/social-media-profile-generator.ts +22 -0
- package/src/i18n/messages/zh-tw/tools/social-platform-asset-adapter.ts +284 -0
- package/src/i18n/messages/zh-tw/tools/social-profile-kit.ts +150 -0
- package/src/i18n/messages/zh-tw/tools/startup-idea-research.ts +165 -0
- package/src/i18n/messages/zh-tw/tools/tarot-reading.ts +228 -0
- package/src/i18n/messages/zh-tw/tools/twitter-gif-downloader.ts +138 -0
- package/src/i18n/messages/zh-tw/tools/user-interview-insight-report-generator.ts +224 -0
- package/src/i18n/messages/zh-tw/tools/user-interview-record-generator.ts +226 -0
- package/src/i18n/messages/zh-tw/tools-ui.ts +525 -0
- package/src/index.ts +15 -0
- package/src/lib/activity-task-handoff.ts +404 -0
- package/src/lib/api/errors.ts +39 -0
- package/src/lib/app-origin.ts +30 -0
- package/src/lib/fact-claim-contract.ts +68 -0
- package/src/lib/fake-delay.ts +3 -0
- package/src/lib/file/download.ts +10 -0
- package/src/lib/routing/locale-paths.ts +27 -0
- package/src/lib/seo-meta.ts +3 -0
- package/src/lib/tools/app-icon-resizer-schema.ts +191 -0
- package/src/lib/tools/app-store-screenshot-generator-schema.ts +185 -0
- package/src/lib/tools/build-in-public-story-generator-schema.ts +67 -0
- package/src/lib/tools/build-in-public-story-generator.format.ts +507 -0
- package/src/lib/tools/cold-start-channel-selector-schema.ts +147 -0
- package/src/lib/tools/commercial-bp-generator-schema.ts +222 -0
- package/src/lib/tools/community-feedback-inbox-schema.ts +125 -0
- package/src/lib/tools/community-feedback-inbox.format.ts +764 -0
- package/src/lib/tools/community-pain-point-extractor-schema.ts +166 -0
- package/src/lib/tools/community-post-risk-checker-schema.ts +99 -0
- package/src/lib/tools/community-post-risk-checker.format.ts +557 -0
- package/src/lib/tools/competitor-analysis-schema.ts +144 -0
- package/src/lib/tools/draft-preview-access.ts +3 -0
- package/src/lib/tools/draft-store.ts +69 -0
- package/src/lib/tools/email-template-generator-schema.ts +124 -0
- package/src/lib/tools/gtm-checklist-copy.ts +57 -0
- package/src/lib/tools/gtm-checklist-structure.ts +37 -0
- package/src/lib/tools/image-download.ts +68 -0
- package/src/lib/tools/input-quality.ts +97 -0
- package/src/lib/tools/logo-generator-schema.ts +224 -0
- package/src/lib/tools/market-forecast-report-schema.ts +299 -0
- package/src/lib/tools/mcp-registry.ts +16 -0
- package/src/lib/tools/mcp-types.ts +18 -0
- package/src/lib/tools/million-user-growth-test-schema.ts +49 -0
- package/src/lib/tools/million-user-growth-test.ts +181 -0
- package/src/lib/tools/og-image-generator-render.ts +448 -0
- package/src/lib/tools/og-image-generator-schema.ts +140 -0
- package/src/lib/tools/ops-catalog.ts +102 -0
- package/src/lib/tools/pitch-deck-generator-schema.ts +182 -0
- package/src/lib/tools/pitch-deck-package.ts +0 -0
- package/src/lib/tools/product-agreement-generator-schema.ts +168 -0
- package/src/lib/tools/product-hunt-launch-planner-schema.ts +136 -0
- package/src/lib/tools/product-hunt-launch-planner.format.ts +72 -0
- package/src/lib/tools/product-name-generator-schema.ts +563 -0
- package/src/lib/tools/product-poster-generator-schema.ts +215 -0
- package/src/lib/tools/product-ui-generator-schema.ts +157 -0
- package/src/lib/tools/reddit-pain-point-finder-schema.ts +88 -0
- package/src/lib/tools/resolve-tool-locale-copy.ts +75 -0
- package/src/lib/tools/slug.ts +60 -0
- package/src/lib/tools/social-listening-research-report-schema.ts +105 -0
- package/src/lib/tools/social-platform-asset-adapter-schema.ts +152 -0
- package/src/lib/tools/social-profile-kit.constants.ts +12 -0
- package/src/lib/tools/social-profile-kit.generation.ts +257 -0
- package/src/lib/tools/social-profile-kit.input.ts +290 -0
- package/src/lib/tools/social-profile-kit.manual.ts +481 -0
- package/src/lib/tools/social-profile-kit.package.ts +220 -0
- package/src/lib/tools/social-profile-kit.schema.ts +328 -0
- package/src/lib/tools/startup-idea-research-schema.ts +107 -0
- package/src/lib/tools/tarot-imagery.ts +89 -0
- package/src/lib/tools/tarot-reading.ts +1044 -0
- package/src/lib/tools/tool-message-keys.ts +18 -0
- package/src/lib/tools/types.ts +32 -0
- package/src/lib/tools/user-interview.format.ts +147 -0
- package/src/lib/tools/user-interview.generation.ts +670 -0
- package/src/lib/tools/user-interview.schema.ts +192 -0
- package/src/lib/zip.ts +106 -0
- package/src/services/tools/handlers/gtm-readiness-checklist.ts +71 -0
- package/src/services/tools/handlers/types.ts +7 -0
|
@@ -0,0 +1,1044 @@
|
|
|
1
|
+
import type { AppLocale } from "../../i18n/locale";
|
|
2
|
+
|
|
3
|
+
import { getTarotVisibleImagery } from "./tarot-imagery";
|
|
4
|
+
|
|
5
|
+
type TarotCopyLocale = "zh" | "en";
|
|
6
|
+
|
|
7
|
+
export type TarotQuestionType =
|
|
8
|
+
| "general"
|
|
9
|
+
| "love"
|
|
10
|
+
| "career"
|
|
11
|
+
| "money"
|
|
12
|
+
| "decision"
|
|
13
|
+
| "self";
|
|
14
|
+
|
|
15
|
+
export type TarotPosition = "past" | "present" | "future";
|
|
16
|
+
export type TarotOrientation = "upright" | "reversed";
|
|
17
|
+
type TarotSuit = "wands" | "cups" | "swords" | "pentacles";
|
|
18
|
+
type MinorRank =
|
|
19
|
+
| "ace"
|
|
20
|
+
| "two"
|
|
21
|
+
| "three"
|
|
22
|
+
| "four"
|
|
23
|
+
| "five"
|
|
24
|
+
| "six"
|
|
25
|
+
| "seven"
|
|
26
|
+
| "eight"
|
|
27
|
+
| "nine"
|
|
28
|
+
| "ten"
|
|
29
|
+
| "page"
|
|
30
|
+
| "knight"
|
|
31
|
+
| "queen"
|
|
32
|
+
| "king";
|
|
33
|
+
|
|
34
|
+
export type TarotCardDraw = {
|
|
35
|
+
id: string;
|
|
36
|
+
position: TarotPosition;
|
|
37
|
+
orientation: TarotOrientation;
|
|
38
|
+
name: string;
|
|
39
|
+
visibleImagery: string;
|
|
40
|
+
meaning: string;
|
|
41
|
+
theme: string;
|
|
42
|
+
keywords: string[];
|
|
43
|
+
positionLens: string;
|
|
44
|
+
questionConnection: string;
|
|
45
|
+
advice: string;
|
|
46
|
+
imageSrc: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type TarotReading = {
|
|
50
|
+
question: string;
|
|
51
|
+
questionType: TarotQuestionType;
|
|
52
|
+
cards: TarotCardDraw[];
|
|
53
|
+
overallStory: string;
|
|
54
|
+
adjustment: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type LocalizedCard = {
|
|
58
|
+
name: string;
|
|
59
|
+
theme: string;
|
|
60
|
+
upright: string;
|
|
61
|
+
reversed: string;
|
|
62
|
+
advice: string;
|
|
63
|
+
keywords: string[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type TarotCard = {
|
|
67
|
+
id: string;
|
|
68
|
+
zh: LocalizedCard;
|
|
69
|
+
en: LocalizedCard;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const majorImageById: Record<string, string> = {
|
|
73
|
+
fool: "maj00.jpg",
|
|
74
|
+
magician: "maj01.jpg",
|
|
75
|
+
"high-priestess": "maj02.jpg",
|
|
76
|
+
empress: "maj03.jpg",
|
|
77
|
+
emperor: "maj04.jpg",
|
|
78
|
+
hierophant: "maj05.jpg",
|
|
79
|
+
lovers: "maj06.jpg",
|
|
80
|
+
chariot: "maj07.jpg",
|
|
81
|
+
strength: "maj08.jpg",
|
|
82
|
+
hermit: "maj09.jpg",
|
|
83
|
+
"wheel-of-fortune": "maj10.jpg",
|
|
84
|
+
justice: "maj11.jpg",
|
|
85
|
+
"hanged-man": "maj12.jpg",
|
|
86
|
+
death: "maj13.jpg",
|
|
87
|
+
temperance: "maj14.jpg",
|
|
88
|
+
devil: "maj15.jpg",
|
|
89
|
+
tower: "maj16.jpg",
|
|
90
|
+
star: "maj17.jpg",
|
|
91
|
+
moon: "maj18.jpg",
|
|
92
|
+
sun: "maj19.jpg",
|
|
93
|
+
judgement: "maj20.jpg",
|
|
94
|
+
world: "maj21.jpg",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const rankImageNumber: Record<MinorRank, string> = {
|
|
98
|
+
ace: "01",
|
|
99
|
+
two: "02",
|
|
100
|
+
three: "03",
|
|
101
|
+
four: "04",
|
|
102
|
+
five: "05",
|
|
103
|
+
six: "06",
|
|
104
|
+
seven: "07",
|
|
105
|
+
eight: "08",
|
|
106
|
+
nine: "09",
|
|
107
|
+
ten: "10",
|
|
108
|
+
page: "11",
|
|
109
|
+
knight: "12",
|
|
110
|
+
queen: "13",
|
|
111
|
+
king: "14",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const suitImagePrefix: Record<TarotSuit, string> = {
|
|
115
|
+
wands: "wands",
|
|
116
|
+
cups: "cups",
|
|
117
|
+
swords: "swords",
|
|
118
|
+
pentacles: "pents",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const suitCopy: Record<
|
|
122
|
+
TarotSuit,
|
|
123
|
+
{
|
|
124
|
+
zhName: string;
|
|
125
|
+
enName: string;
|
|
126
|
+
zhDomain: string;
|
|
127
|
+
enDomain: string;
|
|
128
|
+
zhKeywords: string[];
|
|
129
|
+
enKeywords: string[];
|
|
130
|
+
}
|
|
131
|
+
> = {
|
|
132
|
+
wands: {
|
|
133
|
+
zhName: "权杖",
|
|
134
|
+
enName: "Wands",
|
|
135
|
+
zhDomain: "行动、热情、创造力和推进速度",
|
|
136
|
+
enDomain: "action, heat, creativity, and momentum",
|
|
137
|
+
zhKeywords: ["行动", "热情", "创造"],
|
|
138
|
+
enKeywords: ["action", "spark", "creation"],
|
|
139
|
+
},
|
|
140
|
+
cups: {
|
|
141
|
+
zhName: "圣杯",
|
|
142
|
+
enName: "Cups",
|
|
143
|
+
zhDomain: "情绪、关系、亲密感和内在回应",
|
|
144
|
+
enDomain: "emotion, relationship, intimacy, and inner response",
|
|
145
|
+
zhKeywords: ["情绪", "关系", "感受"],
|
|
146
|
+
enKeywords: ["emotion", "relationship", "feeling"],
|
|
147
|
+
},
|
|
148
|
+
swords: {
|
|
149
|
+
zhName: "宝剑",
|
|
150
|
+
enName: "Swords",
|
|
151
|
+
zhDomain: "想法、沟通、判断和冲突处理",
|
|
152
|
+
enDomain: "thought, communication, judgment, and conflict style",
|
|
153
|
+
zhKeywords: ["思考", "沟通", "判断"],
|
|
154
|
+
enKeywords: ["thought", "words", "clarity"],
|
|
155
|
+
},
|
|
156
|
+
pentacles: {
|
|
157
|
+
zhName: "星币",
|
|
158
|
+
enName: "Pentacles",
|
|
159
|
+
zhDomain: "资源、金钱、身体、时间和现实执行",
|
|
160
|
+
enDomain: "resources, money, body, time, and practical execution",
|
|
161
|
+
zhKeywords: ["资源", "现实", "执行"],
|
|
162
|
+
enKeywords: ["resources", "practice", "stability"],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const rankCopy: Record<
|
|
167
|
+
MinorRank,
|
|
168
|
+
{
|
|
169
|
+
zhName: string;
|
|
170
|
+
enName: string;
|
|
171
|
+
zhTheme: string;
|
|
172
|
+
enTheme: string;
|
|
173
|
+
zhUpright: string;
|
|
174
|
+
zhReversed: string;
|
|
175
|
+
zhAdvice: string;
|
|
176
|
+
enUpright: string;
|
|
177
|
+
enReversed: string;
|
|
178
|
+
enAdvice: string;
|
|
179
|
+
}
|
|
180
|
+
> = {
|
|
181
|
+
ace: {
|
|
182
|
+
zhName: "首牌",
|
|
183
|
+
enName: "Ace",
|
|
184
|
+
zhTheme: "新的种子与可启动的机会",
|
|
185
|
+
enTheme: "A new seed and a usable opening",
|
|
186
|
+
zhUpright: "一个新的入口正在出现,先把它当成可验证的小起点。",
|
|
187
|
+
zhReversed: "机会还没真正落地,可能需要先补足动机、资源或时机。",
|
|
188
|
+
zhAdvice: "把灵感变成一个小动作,先确认它是否能产生真实反馈。",
|
|
189
|
+
enUpright: "A new opening is appearing; treat it as a small testable beginning.",
|
|
190
|
+
enReversed: "The opening may not be grounded yet; motivation, resources, or timing need attention first.",
|
|
191
|
+
enAdvice: "Turn the spark into one small action and see whether it creates real feedback.",
|
|
192
|
+
},
|
|
193
|
+
two: {
|
|
194
|
+
zhName: "二",
|
|
195
|
+
enName: "Two",
|
|
196
|
+
zhTheme: "选择、平衡与初步规划",
|
|
197
|
+
enTheme: "Choice, balance, and early planning",
|
|
198
|
+
zhUpright: "你正在比较两种力量或路径,清楚标准比急着选更重要。",
|
|
199
|
+
zhReversed: "犹豫可能来自信息不完整,或你还没有承认真正的优先级。",
|
|
200
|
+
zhAdvice: "写下不可妥协的标准,再看哪个选项更能承担后果。",
|
|
201
|
+
enUpright: "You are comparing two forces or paths; the standard matters more than rushing the choice.",
|
|
202
|
+
enReversed: "Hesitation may come from missing information or an unacknowledged priority.",
|
|
203
|
+
enAdvice: "Name the non-negotiable standard, then choose the option whose consequences you can own.",
|
|
204
|
+
},
|
|
205
|
+
three: {
|
|
206
|
+
zhName: "三",
|
|
207
|
+
enName: "Three",
|
|
208
|
+
zhTheme: "扩展、协作与看见下一步",
|
|
209
|
+
enTheme: "Expansion, collaboration, and the next horizon",
|
|
210
|
+
zhUpright: "事情开始向外展开,你需要看见更大的格局和可协作的人。",
|
|
211
|
+
zhReversed: "扩张还不稳,可能是合作节奏或期待没有对齐。",
|
|
212
|
+
zhAdvice: "先对齐目标、角色和边界,再扩大行动范围。",
|
|
213
|
+
enUpright: "The matter is starting to expand; look for the wider map and useful collaborators.",
|
|
214
|
+
enReversed: "Expansion is not steady yet; collaboration rhythm or expectations may be misaligned.",
|
|
215
|
+
enAdvice: "Align goals, roles, and boundaries before widening the effort.",
|
|
216
|
+
},
|
|
217
|
+
four: {
|
|
218
|
+
zhName: "四",
|
|
219
|
+
enName: "Four",
|
|
220
|
+
zhTheme: "稳定、休整与阶段性安全感",
|
|
221
|
+
enTheme: "Stability, pause, and temporary security",
|
|
222
|
+
zhUpright: "一个阶段需要稳定下来,先让已有成果站稳。",
|
|
223
|
+
zhReversed: "稳定可能变成停滞,或你还没有真正允许自己休整。",
|
|
224
|
+
zhAdvice: "保留能支撑你的结构,同时检查哪里需要重新流动。",
|
|
225
|
+
enUpright: "A phase needs to stabilize; let the current gains stand on firmer ground.",
|
|
226
|
+
enReversed: "Stability may be turning into stagnation, or you may not be allowing rest.",
|
|
227
|
+
enAdvice: "Keep the structures that support you while checking where movement needs to return.",
|
|
228
|
+
},
|
|
229
|
+
five: {
|
|
230
|
+
zhName: "五",
|
|
231
|
+
enName: "Five",
|
|
232
|
+
zhTheme: "摩擦、失衡与必要调整",
|
|
233
|
+
enTheme: "Friction, imbalance, and needed adjustment",
|
|
234
|
+
zhUpright: "冲突或失衡已经显现,它提醒你不能继续照旧推进。",
|
|
235
|
+
zhReversed: "你可能正在从摩擦中恢复,也可能在避免一次必要对话。",
|
|
236
|
+
zhAdvice: "先处理最具体的矛盾,不要把所有不安混成一团。",
|
|
237
|
+
enUpright: "Friction or imbalance is visible; continuing as usual may not work.",
|
|
238
|
+
enReversed: "You may be recovering from friction or avoiding a necessary conversation.",
|
|
239
|
+
enAdvice: "Deal with the most specific tension first instead of blending all unease together.",
|
|
240
|
+
},
|
|
241
|
+
six: {
|
|
242
|
+
zhName: "六",
|
|
243
|
+
enName: "Six",
|
|
244
|
+
zhTheme: "回应、修复与阶段性认可",
|
|
245
|
+
enTheme: "Response, repair, and partial recognition",
|
|
246
|
+
zhUpright: "你开始得到回应或看见回报,但仍要分辨它是否可持续。",
|
|
247
|
+
zhReversed: "认可或修复来得不完整,可能需要降低对外界反馈的依赖。",
|
|
248
|
+
zhAdvice: "接住好的反馈,同时把标准放回自己能掌控的行动上。",
|
|
249
|
+
enUpright: "A response or reward is arriving, but check whether it is sustainable.",
|
|
250
|
+
enReversed: "Recognition or repair may be incomplete; reduce dependence on outside validation.",
|
|
251
|
+
enAdvice: "Receive useful feedback while returning the standard to actions you control.",
|
|
252
|
+
},
|
|
253
|
+
seven: {
|
|
254
|
+
zhName: "七",
|
|
255
|
+
enName: "Seven",
|
|
256
|
+
zhTheme: "防守、评估与坚持位置",
|
|
257
|
+
enTheme: "Defense, assessment, and holding position",
|
|
258
|
+
zhUpright: "你需要守住一个位置,同时判断哪些压力值得回应。",
|
|
259
|
+
zhReversed: "过度防御或缺少底气,可能让你把所有声音都当成威胁。",
|
|
260
|
+
zhAdvice: "区分真正的风险和只是让你紧张的噪音。",
|
|
261
|
+
enUpright: "You need to hold a position while deciding which pressures deserve response.",
|
|
262
|
+
enReversed: "Over-defense or low confidence may make every voice feel like a threat.",
|
|
263
|
+
enAdvice: "Separate real risk from noise that only makes you tense.",
|
|
264
|
+
},
|
|
265
|
+
eight: {
|
|
266
|
+
zhName: "八",
|
|
267
|
+
enName: "Eight",
|
|
268
|
+
zhTheme: "速度、重复练习与推进中的秩序",
|
|
269
|
+
enTheme: "Speed, repetition, and order in motion",
|
|
270
|
+
zhUpright: "事情在加速,关键是保持节奏和清晰沟通。",
|
|
271
|
+
zhReversed: "速度可能被延迟、卡顿或重复错误拖住。",
|
|
272
|
+
zhAdvice: "减少同时推进的事项,让最重要的一条线先跑通。",
|
|
273
|
+
enUpright: "The matter is accelerating; cadence and clear communication matter now.",
|
|
274
|
+
enReversed: "Delay, friction, or repeated mistakes may be slowing the momentum.",
|
|
275
|
+
enAdvice: "Reduce parallel efforts and let the most important line run cleanly first.",
|
|
276
|
+
},
|
|
277
|
+
nine: {
|
|
278
|
+
zhName: "九",
|
|
279
|
+
enName: "Nine",
|
|
280
|
+
zhTheme: "积累、边界与接近完成前的韧性",
|
|
281
|
+
enTheme: "Accumulation, boundaries, and resilience near completion",
|
|
282
|
+
zhUpright: "你已经走了很远,接下来要保护能量和关键成果。",
|
|
283
|
+
zhReversed: "疲惫感可能让你低估自己,也提醒你不要硬撑到底。",
|
|
284
|
+
zhAdvice: "保留必要边界,给恢复留位置,别把韧性误当成无限消耗。",
|
|
285
|
+
enUpright: "You have come far; protect your energy and the important gains.",
|
|
286
|
+
enReversed: "Fatigue may make you underestimate yourself and warns against pushing past capacity.",
|
|
287
|
+
enAdvice: "Keep necessary boundaries and make room for recovery; resilience is not endless depletion.",
|
|
288
|
+
},
|
|
289
|
+
ten: {
|
|
290
|
+
zhName: "十",
|
|
291
|
+
enName: "Ten",
|
|
292
|
+
zhTheme: "完成、负荷与收束方式",
|
|
293
|
+
enTheme: "Completion, burden, and how to close",
|
|
294
|
+
zhUpright: "这件事接近一个结果,但伴随责任、负荷或收尾压力。",
|
|
295
|
+
zhReversed: "你可能在卸下过重负担,或需要承认某种方式已经到头。",
|
|
296
|
+
zhAdvice: "列出必须完成、可以委托、应该停止的三类事项。",
|
|
297
|
+
enUpright: "This is nearing an outcome, with responsibility, load, or closing pressure attached.",
|
|
298
|
+
enReversed: "You may be releasing excess burden or admitting that a method has run its course.",
|
|
299
|
+
enAdvice: "List what must be finished, delegated, and stopped.",
|
|
300
|
+
},
|
|
301
|
+
page: {
|
|
302
|
+
zhName: "侍从",
|
|
303
|
+
enName: "Page",
|
|
304
|
+
zhTheme: "学习、试探与新信息",
|
|
305
|
+
enTheme: "Learning, experimentation, and new information",
|
|
306
|
+
zhUpright: "以学习者姿态进入,会比立刻证明自己更有效。",
|
|
307
|
+
zhReversed: "经验不足或消息混乱,可能让判断过早成形。",
|
|
308
|
+
zhAdvice: "先提问、观察和记录,再做最终判断。",
|
|
309
|
+
enUpright: "Entering as a learner may work better than trying to prove yourself immediately.",
|
|
310
|
+
enReversed: "Inexperience or messy information may be forming a judgment too early.",
|
|
311
|
+
enAdvice: "Ask, observe, and document before making the final call.",
|
|
312
|
+
},
|
|
313
|
+
knight: {
|
|
314
|
+
zhName: "骑士",
|
|
315
|
+
enName: "Knight",
|
|
316
|
+
zhTheme: "推进、冲劲与行动风格",
|
|
317
|
+
enTheme: "Momentum, drive, and action style",
|
|
318
|
+
zhUpright: "行动力正在增强,但要注意速度是否服务于目标。",
|
|
319
|
+
zhReversed: "冲动、摇摆或方向不稳,可能让力量用错地方。",
|
|
320
|
+
zhAdvice: "在行动前确认目标、节奏和停止条件。",
|
|
321
|
+
enUpright: "Drive is increasing, but speed needs to serve the goal.",
|
|
322
|
+
enReversed: "Impulse, inconsistency, or unstable direction may send energy to the wrong place.",
|
|
323
|
+
enAdvice: "Before acting, confirm the goal, cadence, and stop condition.",
|
|
324
|
+
},
|
|
325
|
+
queen: {
|
|
326
|
+
zhName: "王后",
|
|
327
|
+
enName: "Queen",
|
|
328
|
+
zhTheme: "成熟承载、感受力与内在掌控",
|
|
329
|
+
enTheme: "Mature holding, receptivity, and inner command",
|
|
330
|
+
zhUpright: "你可以用更成熟的方式承载这件事,而不是被它推着走。",
|
|
331
|
+
zhReversed: "过度吸收他人情绪或忽略自己需求,会削弱你的稳定感。",
|
|
332
|
+
zhAdvice: "先确认自己的状态,再决定能给出多少回应。",
|
|
333
|
+
enUpright: "You can hold this matter with more maturity instead of being pushed around by it.",
|
|
334
|
+
enReversed: "Absorbing too much from others or ignoring your needs may weaken stability.",
|
|
335
|
+
enAdvice: "Check your own state before deciding how much response you can give.",
|
|
336
|
+
},
|
|
337
|
+
king: {
|
|
338
|
+
zhName: "国王",
|
|
339
|
+
enName: "King",
|
|
340
|
+
zhTheme: "领导、边界与稳定掌控",
|
|
341
|
+
enTheme: "Leadership, boundaries, and steady command",
|
|
342
|
+
zhUpright: "现在需要更清楚的主导、标准和长期视角。",
|
|
343
|
+
zhReversed: "掌控可能变成僵硬,或你在逃避承担该承担的责任。",
|
|
344
|
+
zhAdvice: "把规则说清楚,并用稳定行动证明它不是一时情绪。",
|
|
345
|
+
enUpright: "Clearer leadership, standards, and long-range perspective are needed now.",
|
|
346
|
+
enReversed: "Control may be turning rigid, or you may be avoiding responsibility.",
|
|
347
|
+
enAdvice: "State the rule clearly and prove through steady action that it is not a passing mood.",
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const majorArcana: TarotCard[] = [
|
|
352
|
+
{
|
|
353
|
+
id: "fool",
|
|
354
|
+
zh: {
|
|
355
|
+
name: "愚者",
|
|
356
|
+
theme: "新的开始与试探性的勇气",
|
|
357
|
+
upright: "你正在靠近一个新阶段,真正重要的是先迈出可控的一步。",
|
|
358
|
+
reversed: "冲动或准备不足正在放大不确定性,先把最低风险的尝试设计出来。",
|
|
359
|
+
advice: "把目标拆成一次小实验,不要一开始就要求答案完美。",
|
|
360
|
+
keywords: ["开始", "冒险", "轻装上阵"],
|
|
361
|
+
},
|
|
362
|
+
en: {
|
|
363
|
+
name: "The Fool",
|
|
364
|
+
theme: "A fresh start and experimental courage",
|
|
365
|
+
upright: "You are near a new phase, and the useful move is a small, controlled first step.",
|
|
366
|
+
reversed: "Impulse or under-preparation may be raising uncertainty; design the lowest-risk trial first.",
|
|
367
|
+
advice: "Turn the goal into one small experiment instead of demanding a perfect answer.",
|
|
368
|
+
keywords: ["start", "risk", "openness"],
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: "magician",
|
|
373
|
+
zh: {
|
|
374
|
+
name: "魔术师",
|
|
375
|
+
theme: "资源整合与主动创造",
|
|
376
|
+
upright: "你手里已经有可用资源,关键是把它们组织成一个清楚的行动。",
|
|
377
|
+
reversed: "想法很多但力量分散,先排除会消耗注意力的选项。",
|
|
378
|
+
advice: "列出你已经拥有的时间、信息、人脉或工具,并选一个最能产生反馈的动作。",
|
|
379
|
+
keywords: ["资源", "表达", "执行"],
|
|
380
|
+
},
|
|
381
|
+
en: {
|
|
382
|
+
name: "The Magician",
|
|
383
|
+
theme: "Resourcefulness and intentional action",
|
|
384
|
+
upright: "You already have usable resources; the key is arranging them into a clear action.",
|
|
385
|
+
reversed: "There may be many ideas but scattered focus, so remove the options that drain attention.",
|
|
386
|
+
advice: "List the time, information, people, or tools you already have and choose the action most likely to create feedback.",
|
|
387
|
+
keywords: ["resources", "craft", "agency"],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "high-priestess",
|
|
392
|
+
zh: {
|
|
393
|
+
name: "女祭司",
|
|
394
|
+
theme: "直觉、沉默信息与内在判断",
|
|
395
|
+
upright: "有些答案还没有浮到表面,先听见自己真正犹豫的原因。",
|
|
396
|
+
reversed: "你可能忽略了直觉里的警讯,或被外界声音盖过了自己的判断。",
|
|
397
|
+
advice: "暂缓马上定论,写下你已经知道但还没有承认的信息。",
|
|
398
|
+
keywords: ["直觉", "隐藏信息", "观察"],
|
|
399
|
+
},
|
|
400
|
+
en: {
|
|
401
|
+
name: "The High Priestess",
|
|
402
|
+
theme: "Intuition, hidden information, and inner knowing",
|
|
403
|
+
upright: "Some answers have not surfaced yet; listen for the real reason behind your hesitation.",
|
|
404
|
+
reversed: "You may be ignoring a quiet warning or letting outside noise drown out your own judgment.",
|
|
405
|
+
advice: "Pause the final call and write down what you already know but have not admitted.",
|
|
406
|
+
keywords: ["intuition", "silence", "signals"],
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
id: "empress",
|
|
411
|
+
zh: {
|
|
412
|
+
name: "皇后",
|
|
413
|
+
theme: "滋养、关系与自然生长",
|
|
414
|
+
upright: "这件事需要被好好照料,而不是被催促着立刻结果。",
|
|
415
|
+
reversed: "过度付出或缺少边界可能让你很快耗尽。",
|
|
416
|
+
advice: "检查哪些部分需要时间、支持和恢复,也检查你是否给得太多。",
|
|
417
|
+
keywords: ["滋养", "丰盛", "边界"],
|
|
418
|
+
},
|
|
419
|
+
en: {
|
|
420
|
+
name: "The Empress",
|
|
421
|
+
theme: "Nurture, relationship, and organic growth",
|
|
422
|
+
upright: "This matter needs care rather than pressure for an immediate result.",
|
|
423
|
+
reversed: "Over-giving or weak boundaries may drain you quickly.",
|
|
424
|
+
advice: "Notice what needs time, support, and recovery, and where you may be giving too much.",
|
|
425
|
+
keywords: ["nurture", "growth", "boundaries"],
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: "emperor",
|
|
430
|
+
zh: {
|
|
431
|
+
name: "皇帝",
|
|
432
|
+
theme: "结构、责任与稳定边界",
|
|
433
|
+
upright: "现在需要清楚规则和稳定节奏,靠感觉推进会不够。",
|
|
434
|
+
reversed: "控制欲、僵化或逃避责任都可能让问题卡住。",
|
|
435
|
+
advice: "把责任、截止时间和可执行标准写清楚。",
|
|
436
|
+
keywords: ["结构", "责任", "秩序"],
|
|
437
|
+
},
|
|
438
|
+
en: {
|
|
439
|
+
name: "The Emperor",
|
|
440
|
+
theme: "Structure, responsibility, and stable boundaries",
|
|
441
|
+
upright: "Clear rules and a steady cadence matter now; feeling your way through may not be enough.",
|
|
442
|
+
reversed: "Control, rigidity, or avoided responsibility may be keeping things stuck.",
|
|
443
|
+
advice: "Make the owner, deadline, and standard of done explicit.",
|
|
444
|
+
keywords: ["structure", "authority", "standards"],
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: "hierophant",
|
|
449
|
+
zh: {
|
|
450
|
+
name: "教皇",
|
|
451
|
+
theme: "传统、学习与可信框架",
|
|
452
|
+
upright: "借助成熟方法或可信前辈,会比完全自创更稳。",
|
|
453
|
+
reversed: "旧规则可能不再适用,你需要判断哪些规范只是惯性。",
|
|
454
|
+
advice: "先找一个可靠框架,再决定哪里需要调整成适合你的版本。",
|
|
455
|
+
keywords: ["学习", "规则", "传承"],
|
|
456
|
+
},
|
|
457
|
+
en: {
|
|
458
|
+
name: "The Hierophant",
|
|
459
|
+
theme: "Tradition, learning, and trusted frameworks",
|
|
460
|
+
upright: "A proven method or trusted mentor may be steadier than inventing everything alone.",
|
|
461
|
+
reversed: "An old rule may no longer fit; decide which norms are only habit.",
|
|
462
|
+
advice: "Start with a reliable framework, then adapt the parts that need to fit your situation.",
|
|
463
|
+
keywords: ["learning", "systems", "guidance"],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: "lovers",
|
|
468
|
+
zh: {
|
|
469
|
+
name: "恋人",
|
|
470
|
+
theme: "选择、价值一致与关系协商",
|
|
471
|
+
upright: "真正的重点是价值是否一致,而不只是眼前是否吸引。",
|
|
472
|
+
reversed: "摇摆、误解或回避选择会削弱信任。",
|
|
473
|
+
advice: "把你在意的标准说出来,观察对方或环境是否能回应。",
|
|
474
|
+
keywords: ["选择", "关系", "一致"],
|
|
475
|
+
},
|
|
476
|
+
en: {
|
|
477
|
+
name: "The Lovers",
|
|
478
|
+
theme: "Choice, alignment, and relational honesty",
|
|
479
|
+
upright: "The real question is value alignment, not only immediate attraction.",
|
|
480
|
+
reversed: "Ambivalence, misunderstanding, or avoided choice may weaken trust.",
|
|
481
|
+
advice: "Name the standard that matters to you and see whether the other side can meet it.",
|
|
482
|
+
keywords: ["choice", "alignment", "relationship"],
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: "chariot",
|
|
487
|
+
zh: {
|
|
488
|
+
name: "战车",
|
|
489
|
+
theme: "方向感、意志与推进",
|
|
490
|
+
upright: "只要方向统一,你有机会把局面往前推。",
|
|
491
|
+
reversed: "力量在互相拉扯,越急越容易偏航。",
|
|
492
|
+
advice: "先定一个主目标,再把冲突目标放到次要位置。",
|
|
493
|
+
keywords: ["推进", "目标", "掌控"],
|
|
494
|
+
},
|
|
495
|
+
en: {
|
|
496
|
+
name: "The Chariot",
|
|
497
|
+
theme: "Direction, willpower, and momentum",
|
|
498
|
+
upright: "Once the direction is unified, you can move the situation forward.",
|
|
499
|
+
reversed: "Competing forces may be pulling you off course; rushing could worsen it.",
|
|
500
|
+
advice: "Pick one primary goal and move the conflicting goals into a secondary lane.",
|
|
501
|
+
keywords: ["drive", "focus", "momentum"],
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: "strength",
|
|
506
|
+
zh: {
|
|
507
|
+
name: "力量",
|
|
508
|
+
theme: "温柔的掌控与耐心勇气",
|
|
509
|
+
upright: "真正有力量的做法不是压制,而是稳定地安抚和引导。",
|
|
510
|
+
reversed: "自我怀疑或情绪过载可能让你低估自己的承受力。",
|
|
511
|
+
advice: "用温和但坚定的方式处理冲突,先稳住节奏。",
|
|
512
|
+
keywords: ["耐心", "勇气", "自控"],
|
|
513
|
+
},
|
|
514
|
+
en: {
|
|
515
|
+
name: "Strength",
|
|
516
|
+
theme: "Gentle control and patient courage",
|
|
517
|
+
upright: "The strong move is not force; it is steady, calm guidance.",
|
|
518
|
+
reversed: "Self-doubt or emotional overload may be making you underestimate your capacity.",
|
|
519
|
+
advice: "Handle conflict with gentle firmness and stabilize the rhythm first.",
|
|
520
|
+
keywords: ["patience", "courage", "self-control"],
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
id: "hermit",
|
|
525
|
+
zh: {
|
|
526
|
+
name: "隐士",
|
|
527
|
+
theme: "独处、辨认真相与慢下来",
|
|
528
|
+
upright: "你需要一点距离,才能看清自己真正要的是什么。",
|
|
529
|
+
reversed: "过度封闭可能让你错过必要的反馈。",
|
|
530
|
+
advice: "给自己安静时间,同时保留一个可信的外部反馈点。",
|
|
531
|
+
keywords: ["独处", "洞察", "回望"],
|
|
532
|
+
},
|
|
533
|
+
en: {
|
|
534
|
+
name: "The Hermit",
|
|
535
|
+
theme: "Solitude, truth-seeking, and slowing down",
|
|
536
|
+
upright: "A little distance can help you see what you truly want.",
|
|
537
|
+
reversed: "Too much isolation may cut you off from useful feedback.",
|
|
538
|
+
advice: "Give yourself quiet time while keeping one trusted outside feedback point.",
|
|
539
|
+
keywords: ["solitude", "insight", "reflection"],
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
id: "wheel-of-fortune",
|
|
544
|
+
zh: {
|
|
545
|
+
name: "命运之轮",
|
|
546
|
+
theme: "周期变化与时机",
|
|
547
|
+
upright: "局面正在转动,抓住变化中的可行动窗口。",
|
|
548
|
+
reversed: "你可能在和周期对抗,或重复同一种模式。",
|
|
549
|
+
advice: "观察趋势而不是只看单次结果,顺势调整动作。",
|
|
550
|
+
keywords: ["变化", "时机", "周期"],
|
|
551
|
+
},
|
|
552
|
+
en: {
|
|
553
|
+
name: "Wheel of Fortune",
|
|
554
|
+
theme: "Cycles, change, and timing",
|
|
555
|
+
upright: "The situation is moving; look for the actionable window inside the change.",
|
|
556
|
+
reversed: "You may be resisting a cycle or repeating the same pattern.",
|
|
557
|
+
advice: "Watch the trend instead of one isolated outcome, then adapt with the cycle.",
|
|
558
|
+
keywords: ["change", "timing", "pattern"],
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: "justice",
|
|
563
|
+
zh: {
|
|
564
|
+
name: "正义",
|
|
565
|
+
theme: "事实、平衡与后果",
|
|
566
|
+
upright: "事实和责任需要被摆到台面上,情绪不能替代证据。",
|
|
567
|
+
reversed: "判断可能受到偏见、信息不完整或不公平交换的影响。",
|
|
568
|
+
advice: "先核对事实、承诺和代价,再做决定。",
|
|
569
|
+
keywords: ["事实", "公平", "后果"],
|
|
570
|
+
},
|
|
571
|
+
en: {
|
|
572
|
+
name: "Justice",
|
|
573
|
+
theme: "Facts, balance, and consequences",
|
|
574
|
+
upright: "Facts and responsibility need to be visible; emotion cannot replace evidence.",
|
|
575
|
+
reversed: "Bias, incomplete information, or an unfair exchange may be shaping the judgment.",
|
|
576
|
+
advice: "Check the facts, commitments, and costs before deciding.",
|
|
577
|
+
keywords: ["facts", "fairness", "accountability"],
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
id: "hanged-man",
|
|
582
|
+
zh: {
|
|
583
|
+
name: "倒吊人",
|
|
584
|
+
theme: "暂停、换角度与放下控制",
|
|
585
|
+
upright: "主动暂停会带来新的视角,不是所有进展都靠硬推。",
|
|
586
|
+
reversed: "拖延和真正的等待混在一起,容易让你失去主动权。",
|
|
587
|
+
advice: "问自己:现在停下来是在换角度,还是在逃避下一步?",
|
|
588
|
+
keywords: ["暂停", "视角", "放下"],
|
|
589
|
+
},
|
|
590
|
+
en: {
|
|
591
|
+
name: "The Hanged Man",
|
|
592
|
+
theme: "Pause, perspective, and release",
|
|
593
|
+
upright: "A deliberate pause can bring a new angle; not all progress comes from pushing.",
|
|
594
|
+
reversed: "Delay and wise waiting may be mixed together, costing you agency.",
|
|
595
|
+
advice: "Ask whether the pause is giving perspective or avoiding the next step.",
|
|
596
|
+
keywords: ["pause", "perspective", "release"],
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
id: "death",
|
|
601
|
+
zh: {
|
|
602
|
+
name: "死神",
|
|
603
|
+
theme: "结束旧模式与转化",
|
|
604
|
+
upright: "一个旧阶段正在收束,放下不再有效的方式会腾出空间。",
|
|
605
|
+
reversed: "你可能知道该结束什么,却还在用惯性拖住自己。",
|
|
606
|
+
advice: "明确哪一件事应该停止、简化或换一种做法。",
|
|
607
|
+
keywords: ["结束", "转化", "更新"],
|
|
608
|
+
},
|
|
609
|
+
en: {
|
|
610
|
+
name: "Death",
|
|
611
|
+
theme: "Ending an old pattern and transforming",
|
|
612
|
+
upright: "An old phase is closing; releasing what no longer works creates room.",
|
|
613
|
+
reversed: "You may know what needs to end while habit keeps you attached.",
|
|
614
|
+
advice: "Name one thing to stop, simplify, or redesign.",
|
|
615
|
+
keywords: ["ending", "transition", "renewal"],
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
id: "temperance",
|
|
620
|
+
zh: {
|
|
621
|
+
name: "节制",
|
|
622
|
+
theme: "调和、节奏与长期平衡",
|
|
623
|
+
upright: "这件事需要比例感,过快或过满都会破坏平衡。",
|
|
624
|
+
reversed: "节奏失衡,可能是同时追求太多目标。",
|
|
625
|
+
advice: "把强度调低,把节奏调稳,让关系或计划有恢复空间。",
|
|
626
|
+
keywords: ["调和", "节奏", "平衡"],
|
|
627
|
+
},
|
|
628
|
+
en: {
|
|
629
|
+
name: "Temperance",
|
|
630
|
+
theme: "Integration, pacing, and long-term balance",
|
|
631
|
+
upright: "This needs proportion; too fast or too much may break the balance.",
|
|
632
|
+
reversed: "The rhythm is off, perhaps because too many goals are being pursued at once.",
|
|
633
|
+
advice: "Lower the intensity, steady the pace, and leave room for recovery.",
|
|
634
|
+
keywords: ["balance", "pacing", "integration"],
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
id: "devil",
|
|
639
|
+
zh: {
|
|
640
|
+
name: "恶魔",
|
|
641
|
+
theme: "执念、诱惑与看见束缚",
|
|
642
|
+
upright: "某种依赖、执念或短期诱惑正在影响你的自由度。",
|
|
643
|
+
reversed: "你开始看见束缚,也有机会把选择权拿回来。",
|
|
644
|
+
advice: "找出让你说“我没办法”的那条链子,并做一个小的解绑动作。",
|
|
645
|
+
keywords: ["执念", "依赖", "解绑"],
|
|
646
|
+
},
|
|
647
|
+
en: {
|
|
648
|
+
name: "The Devil",
|
|
649
|
+
theme: "Attachment, temptation, and seeing the chain",
|
|
650
|
+
upright: "A dependency, fixation, or short-term temptation may be reducing your freedom.",
|
|
651
|
+
reversed: "You are beginning to see the constraint and can reclaim choice.",
|
|
652
|
+
advice: "Find the chain behind the phrase 'I have no choice' and make one small freeing move.",
|
|
653
|
+
keywords: ["attachment", "constraint", "choice"],
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
id: "tower",
|
|
658
|
+
zh: {
|
|
659
|
+
name: "高塔",
|
|
660
|
+
theme: "旧结构松动与必要的清醒",
|
|
661
|
+
upright: "某个不稳的结构可能被现实戳破,这会难受,但也让真相更清楚。",
|
|
662
|
+
reversed: "你可能已经感觉到问题,却还在避免面对。",
|
|
663
|
+
advice: "先处理最明显的裂缝,不要等它变成更大的崩塌。",
|
|
664
|
+
keywords: ["清醒", "破局", "重建"],
|
|
665
|
+
},
|
|
666
|
+
en: {
|
|
667
|
+
name: "The Tower",
|
|
668
|
+
theme: "A shaky structure and necessary clarity",
|
|
669
|
+
upright: "A weak structure may be exposed by reality; uncomfortable, but clarifying.",
|
|
670
|
+
reversed: "You may already sense the issue while trying not to face it.",
|
|
671
|
+
advice: "Address the clearest crack before it becomes a larger collapse.",
|
|
672
|
+
keywords: ["clarity", "disruption", "rebuild"],
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
id: "star",
|
|
677
|
+
zh: {
|
|
678
|
+
name: "星星",
|
|
679
|
+
theme: "希望、修复与长线信念",
|
|
680
|
+
upright: "希望不是空想,而是你愿意慢慢修复的方向。",
|
|
681
|
+
reversed: "失望让视野变窄,先恢复能量再判断未来。",
|
|
682
|
+
advice: "给自己一个温和但持续的恢复计划。",
|
|
683
|
+
keywords: ["希望", "修复", "愿景"],
|
|
684
|
+
},
|
|
685
|
+
en: {
|
|
686
|
+
name: "The Star",
|
|
687
|
+
theme: "Hope, healing, and long-range faith",
|
|
688
|
+
upright: "Hope is not fantasy; it is the direction you are willing to repair toward.",
|
|
689
|
+
reversed: "Disappointment may be narrowing your view; restore energy before judging the future.",
|
|
690
|
+
advice: "Give yourself a gentle but consistent recovery plan.",
|
|
691
|
+
keywords: ["hope", "healing", "vision"],
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
id: "moon",
|
|
696
|
+
zh: {
|
|
697
|
+
name: "月亮",
|
|
698
|
+
theme: "迷雾、情绪投射与不确定",
|
|
699
|
+
upright: "信息还不够清楚,情绪和想象可能正在填补空白。",
|
|
700
|
+
reversed: "迷雾开始散开,但仍需要验证而不是猜测。",
|
|
701
|
+
advice: "把事实、猜测和恐惧分成三列,再看下一步。",
|
|
702
|
+
keywords: ["不确定", "梦境", "投射"],
|
|
703
|
+
},
|
|
704
|
+
en: {
|
|
705
|
+
name: "The Moon",
|
|
706
|
+
theme: "Fog, brandion, and uncertainty",
|
|
707
|
+
upright: "The information is not clear yet; emotion and imagination may be filling the gaps.",
|
|
708
|
+
reversed: "The fog is thinning, but verification still matters more than guessing.",
|
|
709
|
+
advice: "Separate facts, assumptions, and fears into three columns before the next move.",
|
|
710
|
+
keywords: ["uncertainty", "brandion", "intuition"],
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
id: "sun",
|
|
715
|
+
zh: {
|
|
716
|
+
name: "太阳",
|
|
717
|
+
theme: "清晰、生命力与公开表达",
|
|
718
|
+
upright: "事情有机会变得更明朗,也更适合坦诚表达。",
|
|
719
|
+
reversed: "快乐或信心被压住了,可能需要先处理疲惫或自我否定。",
|
|
720
|
+
advice: "选择一个能让事情更透明、更简单的动作。",
|
|
721
|
+
keywords: ["清晰", "自信", "表达"],
|
|
722
|
+
},
|
|
723
|
+
en: {
|
|
724
|
+
name: "The Sun",
|
|
725
|
+
theme: "Clarity, vitality, and open expression",
|
|
726
|
+
upright: "The matter can become clearer and more open to honest expression.",
|
|
727
|
+
reversed: "Joy or confidence may be muted by fatigue or self-doubt.",
|
|
728
|
+
advice: "Choose one action that makes the situation more transparent and simple.",
|
|
729
|
+
keywords: ["clarity", "confidence", "warmth"],
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
id: "judgement",
|
|
734
|
+
zh: {
|
|
735
|
+
name: "审判",
|
|
736
|
+
theme: "复盘、召唤与重新选择",
|
|
737
|
+
upright: "过去的经验正在召唤你做一次更成熟的选择。",
|
|
738
|
+
reversed: "自责或逃避复盘会让旧问题重复出现。",
|
|
739
|
+
advice: "把这件事当作一次复盘:学到什么,下一次怎么不同?",
|
|
740
|
+
keywords: ["复盘", "觉醒", "选择"],
|
|
741
|
+
},
|
|
742
|
+
en: {
|
|
743
|
+
name: "Judgement",
|
|
744
|
+
theme: "Review, calling, and choosing again",
|
|
745
|
+
upright: "Past experience is asking you to make a more mature choice.",
|
|
746
|
+
reversed: "Self-blame or avoiding review may cause the old issue to repeat.",
|
|
747
|
+
advice: "Treat this as a review: what did you learn, and what changes next time?",
|
|
748
|
+
keywords: ["review", "calling", "renewal"],
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
id: "world",
|
|
753
|
+
zh: {
|
|
754
|
+
name: "世界",
|
|
755
|
+
theme: "完成、整合与进入下一圈",
|
|
756
|
+
upright: "一个阶段接近整合,你可以把经验整理成新的起点。",
|
|
757
|
+
reversed: "事情还差一个收尾动作,未完成感让你难以进入下一步。",
|
|
758
|
+
advice: "定义什么算完成,然后做那个最后的收束动作。",
|
|
759
|
+
keywords: ["完成", "整合", "循环"],
|
|
760
|
+
},
|
|
761
|
+
en: {
|
|
762
|
+
name: "The World",
|
|
763
|
+
theme: "Completion, integration, and the next cycle",
|
|
764
|
+
upright: "A phase is nearing integration; turn the experience into a new starting point.",
|
|
765
|
+
reversed: "One closing action may be missing, leaving you unable to move on.",
|
|
766
|
+
advice: "Define what 'complete' means and do the final closing action.",
|
|
767
|
+
keywords: ["completion", "integration", "cycle"],
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
];
|
|
771
|
+
|
|
772
|
+
const minorRanks: MinorRank[] = [
|
|
773
|
+
"ace",
|
|
774
|
+
"two",
|
|
775
|
+
"three",
|
|
776
|
+
"four",
|
|
777
|
+
"five",
|
|
778
|
+
"six",
|
|
779
|
+
"seven",
|
|
780
|
+
"eight",
|
|
781
|
+
"nine",
|
|
782
|
+
"ten",
|
|
783
|
+
"page",
|
|
784
|
+
"knight",
|
|
785
|
+
"queen",
|
|
786
|
+
"king",
|
|
787
|
+
];
|
|
788
|
+
const minorSuits: TarotSuit[] = ["wands", "cups", "swords", "pentacles"];
|
|
789
|
+
|
|
790
|
+
const minorArcana: TarotCard[] = minorSuits.flatMap((suit) =>
|
|
791
|
+
minorRanks.map((rank) => {
|
|
792
|
+
const suitInfo = suitCopy[suit];
|
|
793
|
+
const rankInfo = rankCopy[rank];
|
|
794
|
+
|
|
795
|
+
return {
|
|
796
|
+
id: `${suit}-${rank}`,
|
|
797
|
+
zh: {
|
|
798
|
+
name: `${suitInfo.zhName}${rankInfo.zhName}`,
|
|
799
|
+
theme: `${rankInfo.zhTheme}:${suitInfo.zhDomain}`,
|
|
800
|
+
upright: `${rankInfo.zhUpright} 在${suitInfo.zhDomain}这条线上,它更像是可以被你使用的能量。`,
|
|
801
|
+
reversed: `${rankInfo.zhReversed} 在${suitInfo.zhDomain}这条线上,它提醒你先修正节奏或边界。`,
|
|
802
|
+
advice: rankInfo.zhAdvice,
|
|
803
|
+
keywords: [...suitInfo.zhKeywords, rankInfo.zhTheme.split("、")[0]],
|
|
804
|
+
},
|
|
805
|
+
en: {
|
|
806
|
+
name: `${rankInfo.enName} of ${suitInfo.enName}`,
|
|
807
|
+
theme: `${rankInfo.enTheme}: ${suitInfo.enDomain}`,
|
|
808
|
+
upright: `${rankInfo.enUpright} In the field of ${suitInfo.enDomain}, this is energy you can work with.`,
|
|
809
|
+
reversed: `${rankInfo.enReversed} In the field of ${suitInfo.enDomain}, it asks for a rhythm or boundary correction first.`,
|
|
810
|
+
advice: rankInfo.enAdvice,
|
|
811
|
+
keywords: [...suitInfo.enKeywords, rankInfo.enTheme.split(",")[0].toLowerCase()],
|
|
812
|
+
},
|
|
813
|
+
};
|
|
814
|
+
}),
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
const tarotDeck: TarotCard[] = [...majorArcana, ...minorArcana];
|
|
818
|
+
|
|
819
|
+
export function getTarotCardImageSrc(cardId: string): string {
|
|
820
|
+
const majorFile = majorImageById[cardId];
|
|
821
|
+
if (majorFile) return `/brand/tarot-waite/${majorFile}`;
|
|
822
|
+
|
|
823
|
+
const [rawSuit, rawRank] = cardId.split("-");
|
|
824
|
+
if (rawSuit && rawRank && rawSuit in suitImagePrefix && rawRank in rankImageNumber) {
|
|
825
|
+
const suit = rawSuit as TarotSuit;
|
|
826
|
+
const rank = rawRank as MinorRank;
|
|
827
|
+
return `/brand/tarot-waite/${suitImagePrefix[suit]}${rankImageNumber[rank]}.jpg`;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return "/brand/tomako-tarot-card-back.webp";
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const positionCopy: Record<
|
|
834
|
+
TarotCopyLocale,
|
|
835
|
+
Record<TarotPosition, { label: string; lens: string }>
|
|
836
|
+
> = {
|
|
837
|
+
zh: {
|
|
838
|
+
past: { label: "过去", lens: "这张牌描述了问题的根源、旧习惯或已经形成的条件。" },
|
|
839
|
+
present: { label: "现在", lens: "这张牌是当前最需要面对的核心,也是整组牌的行动支点。" },
|
|
840
|
+
future: { label: "未来", lens: "这张牌不是固定未来,而是如果当前模式继续时较可能出现的走向。" },
|
|
841
|
+
},
|
|
842
|
+
en: {
|
|
843
|
+
past: { label: "Past", lens: "This card describes the root, old habit, or condition already shaping the matter." },
|
|
844
|
+
present: { label: "Present", lens: "This card is the active core of the reading and the main hinge for action." },
|
|
845
|
+
future: { label: "Future", lens: "This card is not a fixed future; it shows a likely direction if the current pattern continues." },
|
|
846
|
+
},
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
const frameAdvice: Record<TarotCopyLocale, Record<TarotQuestionType, string>> = {
|
|
850
|
+
zh: {
|
|
851
|
+
general: "把它当作一次自我观察,先处理最清楚的一步。",
|
|
852
|
+
love: "关系问题里,优先看沟通、边界、互相回应和真实行为。",
|
|
853
|
+
career: "事业问题里,优先看资源、节奏、职责和可执行动作。",
|
|
854
|
+
money: "金钱问题里,优先看风险、时间周期、现金流和可验证选择。",
|
|
855
|
+
decision: "选择题里,先明确标准,再看哪条路更能承担后果。",
|
|
856
|
+
self: "自我探索里,重点不是马上改变,而是看见模式后做一个小调整。",
|
|
857
|
+
},
|
|
858
|
+
en: {
|
|
859
|
+
general: "Use this as a self-check and work with the clearest next step first.",
|
|
860
|
+
love: "For relationship questions, look at communication, boundaries, reciprocity, and real behavior.",
|
|
861
|
+
career: "For career questions, prioritize resources, cadence, responsibility, and executable moves.",
|
|
862
|
+
money: "For money questions, prioritize risk, time horizon, cash flow, and verifiable choices.",
|
|
863
|
+
decision: "For decisions, clarify the standard first, then choose the path whose consequences you can own.",
|
|
864
|
+
self: "For self-reflection, the goal is not instant change; it is seeing the pattern and adjusting one small thing.",
|
|
865
|
+
},
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const positions: TarotPosition[] = ["past", "present", "future"];
|
|
869
|
+
|
|
870
|
+
function hashString(value: string): number {
|
|
871
|
+
let hash = 2166136261;
|
|
872
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
873
|
+
hash ^= value.charCodeAt(index);
|
|
874
|
+
hash = Math.imul(hash, 16777619);
|
|
875
|
+
}
|
|
876
|
+
return hash >>> 0;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function mulberry32(seed: number): () => number {
|
|
880
|
+
return () => {
|
|
881
|
+
let t = (seed += 0x6d2b79f5);
|
|
882
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
883
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
884
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function pickCards(seed: string): Array<{ card: TarotCard; orientation: TarotOrientation }> {
|
|
889
|
+
const random = mulberry32(hashString(seed));
|
|
890
|
+
const remaining = [...tarotDeck];
|
|
891
|
+
return positions.map(() => {
|
|
892
|
+
const cardIndex = Math.floor(random() * remaining.length);
|
|
893
|
+
const [card] = remaining.splice(cardIndex, 1);
|
|
894
|
+
const orientation: TarotOrientation = random() > 0.42 ? "upright" : "reversed";
|
|
895
|
+
return { card, orientation };
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function resolveCopyLocale(locale: AppLocale): TarotCopyLocale {
|
|
900
|
+
return locale === "zh" || locale === "zh-tw" ? "zh" : "en";
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function buildQuestionConnection({
|
|
904
|
+
copyLocale,
|
|
905
|
+
position,
|
|
906
|
+
questionType,
|
|
907
|
+
theme,
|
|
908
|
+
}: {
|
|
909
|
+
copyLocale: TarotCopyLocale;
|
|
910
|
+
position: TarotPosition;
|
|
911
|
+
questionType: TarotQuestionType;
|
|
912
|
+
theme: string;
|
|
913
|
+
}) {
|
|
914
|
+
const coreTheme = theme.split(/[::]/, 1)[0];
|
|
915
|
+
if (copyLocale === "en") {
|
|
916
|
+
const subject = questionType === "general" ? "the matter you asked about" : `this ${questionType} question`;
|
|
917
|
+
if (position === "past") return `In the past position, it suggests that ${subject} did not appear overnight. “${coreTheme}” has already been shaping the background, so look for earlier choices or habits that are still affecting the situation.`;
|
|
918
|
+
if (position === "present") return `In the present position, it places “${coreTheme}” at the active center of ${subject}. This is the stance, resource, or response most able to change the direction now.`;
|
|
919
|
+
return `In the future position, it shows “${coreTheme}” as a possible trajectory for ${subject} if the current response continues. Treat it as a condition to verify through later behavior and results, not a fixed outcome.`;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const subject: Record<TarotQuestionType, string> = {
|
|
923
|
+
general: "你问的这件事",
|
|
924
|
+
love: "这次关系问题",
|
|
925
|
+
career: "这次事业或工作问题",
|
|
926
|
+
money: "这次金钱问题",
|
|
927
|
+
decision: "这次选择",
|
|
928
|
+
self: "这次自我探索",
|
|
929
|
+
};
|
|
930
|
+
if (position === "past") return `放在过去位,它说明${subject[questionType]}并非突然形成,“${coreTheme}”已经作为背景条件影响到现在。回看此前哪些具体选择或习惯仍在延续。`;
|
|
931
|
+
if (position === "present") return `放在现在位,它把${subject[questionType]}的焦点落在“${coreTheme}”上:这是此刻最能改变走向的态度、资源或应对方式,需要结合你正在做的具体事情来核对。`;
|
|
932
|
+
return `放在未来位,它把“${coreTheme}”呈现为${subject[questionType]}在当前应对方式延续后可能出现的趋势。请用之后的行为、反馈与结果验证它,而不要把它当成固定结局。`;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export function inferTarotQuestionType(question: string): TarotQuestionType {
|
|
936
|
+
const value = question.trim().toLowerCase();
|
|
937
|
+
const patterns: Array<[TarotQuestionType, RegExp]> = [
|
|
938
|
+
["love", /感情|爱情|恋爱|关系|复合|结婚|婚姻|对象|伴侣|喜欢|暧昧|love|relationship|dating|partner|marriage|crush/],
|
|
939
|
+
["career", /事业|工作|职场|升职|跳槽|面试|创业|项目|career|job|work|promotion|interview|business/],
|
|
940
|
+
["money", /金钱|财运|收入|投资|财富|赚钱|现金流|money|finance|income|investment|wealth|cash/],
|
|
941
|
+
["decision", /选择|决定|要不要|是否|该不该|哪一个|还是|decision|choose|choice|should i|whether/],
|
|
942
|
+
["self", /自己|内心|成长|状态|情绪|人生|方向|self|growth|emotion|purpose|healing/],
|
|
943
|
+
];
|
|
944
|
+
|
|
945
|
+
return patterns.find(([, pattern]) => pattern.test(value))?.[0] ?? "general";
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export function createTarotReading({
|
|
949
|
+
question,
|
|
950
|
+
questionType,
|
|
951
|
+
locale,
|
|
952
|
+
seed,
|
|
953
|
+
}: {
|
|
954
|
+
question: string;
|
|
955
|
+
questionType: TarotQuestionType;
|
|
956
|
+
locale: AppLocale;
|
|
957
|
+
seed: string;
|
|
958
|
+
}): TarotReading {
|
|
959
|
+
const copyLocale = resolveCopyLocale(locale);
|
|
960
|
+
const normalizedQuestion = question.trim();
|
|
961
|
+
const draws = pickCards(`${normalizedQuestion}|${questionType}|${seed}`);
|
|
962
|
+
const cards: TarotCardDraw[] = draws.map(({ card, orientation }, index) => {
|
|
963
|
+
const position = positions[index];
|
|
964
|
+
const copy = card[copyLocale];
|
|
965
|
+
const meaning = orientation === "upright" ? copy.upright : copy.reversed;
|
|
966
|
+
const positionInfo = positionCopy[copyLocale][position];
|
|
967
|
+
|
|
968
|
+
return {
|
|
969
|
+
id: card.id,
|
|
970
|
+
position,
|
|
971
|
+
orientation,
|
|
972
|
+
name: copy.name,
|
|
973
|
+
visibleImagery: getTarotVisibleImagery(card.id, copyLocale === "zh", copy.theme),
|
|
974
|
+
meaning,
|
|
975
|
+
theme: copy.theme,
|
|
976
|
+
keywords: copy.keywords,
|
|
977
|
+
imageSrc: getTarotCardImageSrc(card.id),
|
|
978
|
+
positionLens:
|
|
979
|
+
copyLocale === "zh"
|
|
980
|
+
? `${positionInfo.lens} ${meaning}`
|
|
981
|
+
: `${positionInfo.lens} ${meaning}`,
|
|
982
|
+
questionConnection: buildQuestionConnection({
|
|
983
|
+
copyLocale,
|
|
984
|
+
position,
|
|
985
|
+
questionType,
|
|
986
|
+
theme: copy.theme,
|
|
987
|
+
}),
|
|
988
|
+
advice: copy.advice,
|
|
989
|
+
};
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
const [past, present, future] = cards;
|
|
993
|
+
const coreTheme = (value: string) => value.split(/[::]/, 1)[0];
|
|
994
|
+
const pastTheme = coreTheme(past.theme);
|
|
995
|
+
const futureTheme = coreTheme(future.theme);
|
|
996
|
+
const presentImageBeat = present.visibleImagery.split(/[;。]/, 1)[0];
|
|
997
|
+
const reversedCount = cards.filter((card) => card.orientation === "reversed").length;
|
|
998
|
+
const majorCount = cards.filter((card) => majorImageById[card.id]).length;
|
|
999
|
+
const context = frameAdvice[copyLocale][questionType];
|
|
1000
|
+
const paceNote =
|
|
1001
|
+
reversedCount >= 2
|
|
1002
|
+
? copyLocale === "zh"
|
|
1003
|
+
? "如果你最近觉得事情有些拧巴,这种感觉并非空穴来风。两张以上逆位更像是在说:先别硬推,眼下需要的是校准、补足,或者停下一个已经不顺的动作。"
|
|
1004
|
+
: "If things have felt tangled lately, that feeling is not coming from nowhere. With two or more reversals, the useful response is to recalibrate, repair, or stop forcing what is no longer moving well."
|
|
1005
|
+
: copyLocale === "zh"
|
|
1006
|
+
? "好在这里并没有把路堵死。你手边还有可以推动的条件,只是需要更有意识地使用它们。"
|
|
1007
|
+
: "The path is not closed. There are still workable conditions within reach, but they need to be used deliberately."
|
|
1008
|
+
const weightNote =
|
|
1009
|
+
majorCount >= 2
|
|
1010
|
+
? copyLocale === "zh"
|
|
1011
|
+
? "而且这里出现了不止一张大牌,我会把它看得更深一点:你问的表面是一件具体的事,背后也碰到了你长期做选择和面对变化的方式。"
|
|
1012
|
+
: "With more than one Major Arcana here, I would read this a little deeper: beneath the immediate question sits a longer-standing way of choosing and responding to change."
|
|
1013
|
+
: "";
|
|
1014
|
+
const overallStory =
|
|
1015
|
+
copyLocale === "zh"
|
|
1016
|
+
? `把牌摊开后,我第一眼先停在中间的${present.name}${present.orientation === "upright" ? "正位" : "逆位"}。画面里,${presentImageBeat}。这个姿态让我觉得:你问“${normalizedQuestion}”,重点不只在结果本身,也在于你现在正用什么方式面对它。过去的${past.name}${past.orientation === "upright" ? "正位" : "逆位"}像是在交代“${pastTheme}”怎样一路影响到今天;未来的${future.name}${future.orientation === "upright" ? "正位" : "逆位"}则把“${futureTheme}”摆在前方,但没有把结局写死。${paceNote}${weightNote}`
|
|
1017
|
+
: `When I lay these cards together, my eye goes first to ${present.name} ${present.orientation}. For “${normalizedQuestion},” the issue is not only the outcome; it is also the way you are meeting it now. ${past.name} ${past.orientation} shows how “${pastTheme}” has carried into today, while ${future.name} ${future.orientation} places “${futureTheme}” on the horizon without fixing the ending. ${paceNote}${weightNote ? ` ${weightNote}` : ""}`;
|
|
1018
|
+
|
|
1019
|
+
const realityAction: Record<TarotQuestionType, string> = {
|
|
1020
|
+
general: `把“${normalizedQuestion}”拆成一个可以观察的信号:接下来发生什么具体行为、反馈或结果,才算事情真的在变化。`,
|
|
1021
|
+
love: `回到“${normalizedQuestion}”,先记录实际发生的沟通、回应和边界行为;不要只用一时的热度、冷淡或猜测替对方下结论。`,
|
|
1022
|
+
career: `回到“${normalizedQuestion}”,为下一步写清负责人、截止时间和完成标准,再看它是否真正带来职责、资源或结果上的变化。`,
|
|
1023
|
+
money: `回到“${normalizedQuestion}”,先把赚钱拆成四个可核对的数字:新增收入、已确认成交或回款、必要成本,以及你能承受的最大损失。`,
|
|
1024
|
+
decision: `回到“${normalizedQuestion}”,先写下两个不能妥协的标准,再比较每个选项的真实成本、退出条件和你愿意承担的后果。`,
|
|
1025
|
+
self: `回到“${normalizedQuestion}”,选一个最常重复的场景,记录当时的情绪、行为和边界,观察下一次能否做出一个不同的小选择。`,
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const adjustment =
|
|
1029
|
+
copyLocale === "zh"
|
|
1030
|
+
? `接下来先别急着把所有问题一次解决。${realityAction[questionType]} 我更建议你先做一件可以验证的小事:${present.advice} 等同一个问题再次出现时,停一下问自己:眼前有没有新的事实,还是我只是在重复消耗?如果没有可见反馈,就把动作缩小,并为它设一个明确的停止条件。${context}`
|
|
1031
|
+
: `Do not try to solve everything at once. Put your attention on one move you can verify: ${present.advice} When the same issue returns, pause and ask whether you are making real progress or repeating the same drain. Watch for observable behavior, feedback, or results connected to “${futureTheme}.” If the pattern leaves you numb or tense, make the action smaller and give it a clear stopping condition. ${context}`;
|
|
1032
|
+
|
|
1033
|
+
return {
|
|
1034
|
+
question: normalizedQuestion,
|
|
1035
|
+
questionType,
|
|
1036
|
+
cards,
|
|
1037
|
+
overallStory,
|
|
1038
|
+
adjustment,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
export function getTarotPositionLabel(locale: AppLocale, position: TarotPosition): string {
|
|
1043
|
+
return positionCopy[resolveCopyLocale(locale)][position].label;
|
|
1044
|
+
}
|