@retiregolden/planner-ui 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.
Files changed (298) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +181 -0
  3. package/package.json +77 -0
  4. package/src/App.tsx +246 -0
  5. package/src/RouteErrorBoundary.tsx +45 -0
  6. package/src/assets/hero.png +0 -0
  7. package/src/assets/react.svg +1 -0
  8. package/src/assets/vite.svg +1 -0
  9. package/src/data/fedInvestClient.ts +113 -0
  10. package/src/data/localStore.ts +42 -0
  11. package/src/data/planOrigin.ts +24 -0
  12. package/src/data/planStore.ts +165 -0
  13. package/src/data/v2Backup.ts +101 -0
  14. package/src/import/ImportPage.tsx +347 -0
  15. package/src/import/ReviewChecklistView.tsx +38 -0
  16. package/src/import/brokerCsv.ts +395 -0
  17. package/src/import/csv.ts +133 -0
  18. package/src/import/genericCsv.ts +224 -0
  19. package/src/import/projectionLab.ts +350 -0
  20. package/src/import/reviewChecklist.ts +33 -0
  21. package/src/import/tenForty.ts +275 -0
  22. package/src/index.css +630 -0
  23. package/src/index.ts +16 -0
  24. package/src/learn/ArticleBody.tsx +78 -0
  25. package/src/learn/ArticlePage.tsx +57 -0
  26. package/src/learn/GlossaryPage.tsx +33 -0
  27. package/src/learn/LearnAboutScreen.tsx +41 -0
  28. package/src/learn/LearnCards.tsx +41 -0
  29. package/src/learn/LearnLink.tsx +91 -0
  30. package/src/learn/LearningCenterPage.tsx +114 -0
  31. package/src/learn/SourcesPage.tsx +98 -0
  32. package/src/learn/components/ArticleFigure.tsx +34 -0
  33. package/src/learn/components/ArticleShell.tsx +86 -0
  34. package/src/learn/components/ComparisonTable.tsx +42 -0
  35. package/src/learn/components/FormulaBlock.tsx +34 -0
  36. package/src/learn/components/PurchasingPowerChart.tsx +41 -0
  37. package/src/learn/components/RelatedArticles.tsx +27 -0
  38. package/src/learn/components/ScenarioCard.tsx +24 -0
  39. package/src/learn/components/SourceList.tsx +23 -0
  40. package/src/learn/components/charts.tsx +21 -0
  41. package/src/learn/content/about-retiregolden.ts +100 -0
  42. package/src/learn/content/aca-premium-tax-credits-and-magi.ts +103 -0
  43. package/src/learn/content/account-types-overview.ts +106 -0
  44. package/src/learn/content/after-tax-estate.ts +111 -0
  45. package/src/learn/content/agi-magi-and-taxable-income.ts +112 -0
  46. package/src/learn/content/appealing-irmaa-ssa-44.ts +95 -0
  47. package/src/learn/content/assumption-general-inflation.ts +82 -0
  48. package/src/learn/content/assumption-healthcare-inflation.ts +85 -0
  49. package/src/learn/content/assumption-heir-tax-rate.ts +79 -0
  50. package/src/learn/content/assumption-investment-returns.ts +90 -0
  51. package/src/learn/content/assumption-longevity-planning-age.ts +78 -0
  52. package/src/learn/content/assumption-recent-magi.ts +83 -0
  53. package/src/learn/content/assumption-social-security-cola.ts +89 -0
  54. package/src/learn/content/assumption-social-security-trust-fund.ts +83 -0
  55. package/src/learn/content/assumption-state-tax-override.ts +79 -0
  56. package/src/learn/content/beneficiaries-and-account-titling.ts +99 -0
  57. package/src/learn/content/break-even-useful-lens.ts +94 -0
  58. package/src/learn/content/building-a-retirement-spending-budget.ts +100 -0
  59. package/src/learn/content/cola-and-inflation-protection.ts +102 -0
  60. package/src/learn/content/divorced-spousal-and-survivor-records.ts +104 -0
  61. package/src/learn/content/dynamic-spending-guardrails.ts +90 -0
  62. package/src/learn/content/earnings-test-before-fra.ts +100 -0
  63. package/src/learn/content/employer-match-and-contribution-order.ts +104 -0
  64. package/src/learn/content/examplePlanArticles.ts +525 -0
  65. package/src/learn/content/fees-expense-ratios-and-compounding-drag.ts +98 -0
  66. package/src/learn/content/fi-number-and-four-percent-rule.ts +64 -0
  67. package/src/learn/content/filling-a-tax-bracket-with-roth-conversions.ts +98 -0
  68. package/src/learn/content/funded-ratio.ts +70 -0
  69. package/src/learn/content/healthcare-after-65.ts +103 -0
  70. package/src/learn/content/healthcare-before-65.ts +104 -0
  71. package/src/learn/content/historical-vs-random-return-models.ts +101 -0
  72. package/src/learn/content/how-assumptions-change-the-answer.ts +105 -0
  73. package/src/learn/content/how-much-can-i-spend.ts +105 -0
  74. package/src/learn/content/how-social-security-is-taxed.ts +95 -0
  75. package/src/learn/content/how-the-optimizer-thinks.ts +102 -0
  76. package/src/learn/content/how-the-optimizer-values-after-tax-estate.ts +97 -0
  77. package/src/learn/content/how-to-model-accumulation.ts +67 -0
  78. package/src/learn/content/how-to-read-a-retirement-projection.ts +115 -0
  79. package/src/learn/content/hsas-and-qualified-medical-expenses.ts +108 -0
  80. package/src/learn/content/hsas-as-retirement-accounts.ts +101 -0
  81. package/src/learn/content/inflation-risk.ts +98 -0
  82. package/src/learn/content/inherited-ira-10-year-rule.ts +105 -0
  83. package/src/learn/content/insurance-in-your-retirement-plan.ts +103 -0
  84. package/src/learn/content/irmaa-two-year-lookback.ts +99 -0
  85. package/src/learn/content/long-term-care-costs-and-insurance.ts +103 -0
  86. package/src/learn/content/long-term-care-insurance-as-risk-transfer.ts +98 -0
  87. package/src/learn/content/longevity-risk.ts +99 -0
  88. package/src/learn/content/marginal-vs-effective-tax-rate.ts +98 -0
  89. package/src/learn/content/medicare-part-b-vs-part-d-irmaa.ts +102 -0
  90. package/src/learn/content/mortality-weighted-social-security.ts +113 -0
  91. package/src/learn/content/moving-to-retiregolden.ts +86 -0
  92. package/src/learn/content/niit-high-income-investment-tax.ts +98 -0
  93. package/src/learn/content/ordinary-income-vs-capital-gains.ts +103 -0
  94. package/src/learn/content/paying-conversion-taxes-taxable-vs-ira.ts +102 -0
  95. package/src/learn/content/pensions-and-annuities.ts +101 -0
  96. package/src/learn/content/permanent-life-insurance-in-a-plan.ts +106 -0
  97. package/src/learn/content/pia-aime-and-bend-points.ts +103 -0
  98. package/src/learn/content/planner-overview.ts +106 -0
  99. package/src/learn/content/planning-for-couples-and-survivor-years.ts +108 -0
  100. package/src/learn/content/privacy-what-stays-in-your-browser.ts +99 -0
  101. package/src/learn/content/qcds-qualified-charitable-distributions.ts +101 -0
  102. package/src/learn/content/reading-the-results-page.ts +96 -0
  103. package/src/learn/content/reading-the-social-security-analysis-page.ts +106 -0
  104. package/src/learn/content/real-estate-home-equity-and-debt.ts +100 -0
  105. package/src/learn/content/reports-csv-exports-and-sharing.ts +101 -0
  106. package/src/learn/content/risk-based-guardrails.ts +100 -0
  107. package/src/learn/content/rmds-required-minimum-distributions.ts +100 -0
  108. package/src/learn/content/roth-conversion-basics.ts +104 -0
  109. package/src/learn/content/rsus-and-espp.ts +101 -0
  110. package/src/learn/content/rule-of-55-and-72t.ts +107 -0
  111. package/src/learn/content/savings-rate-biggest-lever.ts +66 -0
  112. package/src/learn/content/seed-your-plan-from-your-tax-return.ts +93 -0
  113. package/src/learn/content/sensitivity-testing-what-changes-the-answer.ts +104 -0
  114. package/src/learn/content/sequence-of-returns-risk.ts +98 -0
  115. package/src/learn/content/social-security-bridge.ts +67 -0
  116. package/src/learn/content/social-security-claiming-age-basics.ts +113 -0
  117. package/src/learn/content/social-security-taxes-vs-benefits.ts +76 -0
  118. package/src/learn/content/spending-profiles-and-the-retirement-smile.ts +92 -0
  119. package/src/learn/content/spousal-and-survivor-benefits.ts +120 -0
  120. package/src/learn/content/ssdi-and-retirement-planning.ts +72 -0
  121. package/src/learn/content/standard-deduction-senior-deduction-and-itemizing.ts +97 -0
  122. package/src/learn/content/state-income-taxes-in-retirement.ts +97 -0
  123. package/src/learn/content/step-up-in-basis.ts +102 -0
  124. package/src/learn/content/survivor-planning-for-couples.ts +110 -0
  125. package/src/learn/content/survivor-spending-in-couple-plans.ts +98 -0
  126. package/src/learn/content/tax-cliffs-and-bracket-edges.ts +105 -0
  127. package/src/learn/content/tax-loss-and-gain-harvesting.ts +99 -0
  128. package/src/learn/content/taxable-brokerage-basis-and-capital-gains.ts +99 -0
  129. package/src/learn/content/three-big-questions-spending-time-risk.ts +103 -0
  130. package/src/learn/content/tips-ladders.ts +92 -0
  131. package/src/learn/content/todays-dollars-vs-future-dollars.ts +107 -0
  132. package/src/learn/content/traditional-vs-roth-contributions.ts +113 -0
  133. package/src/learn/content/troubleshooting-surprising-results.ts +105 -0
  134. package/src/learn/content/trust-fund-haircut-scenarios.ts +101 -0
  135. package/src/learn/content/understanding-monte-carlo-success-rate.ts +118 -0
  136. package/src/learn/content/understanding-your-plan-assumptions.ts +134 -0
  137. package/src/learn/content/using-assumptions-and-provenance.ts +98 -0
  138. package/src/learn/content/using-scenarios-to-compare-choices.ts +99 -0
  139. package/src/learn/content/what-changes-when-you-move-states.ts +141 -0
  140. package/src/learn/content/what-is-fire.ts +65 -0
  141. package/src/learn/content/what-monte-carlo-proves.ts +98 -0
  142. package/src/learn/content/what-retiregolden-models.ts +103 -0
  143. package/src/learn/content/what-retirement-healthcare-really-costs.ts +117 -0
  144. package/src/learn/content/why-95-percent-is-not-a-guarantee.ts +98 -0
  145. package/src/learn/content/why-roth-conversions-raise-other-costs.ts +106 -0
  146. package/src/learn/content/why-small-tax-cliffs-can-matter.ts +109 -0
  147. package/src/learn/content/widows-penalty-and-survivor-brackets.ts +106 -0
  148. package/src/learn/content/withdrawal-order-basics.ts +105 -0
  149. package/src/learn/glossary.ts +191 -0
  150. package/src/learn/inlineMarkdown.tsx +54 -0
  151. package/src/learn/learn.css +537 -0
  152. package/src/learn/learningRegistry.ts +502 -0
  153. package/src/longevity/LongevityResults.tsx +85 -0
  154. package/src/longevity/LongevityWizard.tsx +305 -0
  155. package/src/longevity/constants.ts +15 -0
  156. package/src/longevity/factors.ts +125 -0
  157. package/src/longevity/model.ts +31 -0
  158. package/src/longevity/persistedGuard.ts +129 -0
  159. package/src/longevity/storage.ts +40 -0
  160. package/src/mc/messages.ts +118 -0
  161. package/src/mc/monteCarlo.worker.ts +44 -0
  162. package/src/mc/pool.ts +267 -0
  163. package/src/mc/runRequest.ts +125 -0
  164. package/src/optimize/messages.ts +84 -0
  165. package/src/optimize/optimize.worker.ts +29 -0
  166. package/src/optimize/runOptimize.ts +92 -0
  167. package/src/optimize/runSpendingSolve.ts +47 -0
  168. package/src/optimize/runner.ts +21 -0
  169. package/src/optimize/spendingMessages.ts +44 -0
  170. package/src/optimize/spendingRunner.ts +21 -0
  171. package/src/optimize/spendingSolve.worker.ts +18 -0
  172. package/src/planner/AssumptionsCardPage.tsx +136 -0
  173. package/src/planner/BucketLensCard.tsx +114 -0
  174. package/src/planner/ComparePlansPage.tsx +219 -0
  175. package/src/planner/DisclaimerPage.tsx +88 -0
  176. package/src/planner/HowTestedPage.tsx +159 -0
  177. package/src/planner/LiveStatus.tsx +15 -0
  178. package/src/planner/LongevityModal.tsx +55 -0
  179. package/src/planner/Modal.tsx +97 -0
  180. package/src/planner/MonteCarloPage.tsx +907 -0
  181. package/src/planner/OptimizePage.tsx +611 -0
  182. package/src/planner/PlanContext.tsx +198 -0
  183. package/src/planner/PlanPickerPage.tsx +124 -0
  184. package/src/planner/PlanWorkspace.tsx +290 -0
  185. package/src/planner/ProvenancePanel.tsx +45 -0
  186. package/src/planner/RelocationComparePage.tsx +485 -0
  187. package/src/planner/ReportPage.tsx +375 -0
  188. package/src/planner/ResultsPage.tsx +817 -0
  189. package/src/planner/ScenariosPage.tsx +285 -0
  190. package/src/planner/SocialSecuritySection.tsx +556 -0
  191. package/src/planner/SpendingSolverPage.tsx +512 -0
  192. package/src/planner/SsAnalysisPage.tsx +1134 -0
  193. package/src/planner/SurvivalPercentileModal.tsx +161 -0
  194. package/src/planner/SurvivorTransitionPage.tsx +286 -0
  195. package/src/planner/assumptionsExport.ts +371 -0
  196. package/src/planner/bucketLens.ts +89 -0
  197. package/src/planner/chartFrame.ts +8 -0
  198. package/src/planner/chartStyle.ts +11 -0
  199. package/src/planner/dialogViews.tsx +184 -0
  200. package/src/planner/dialogs.tsx +133 -0
  201. package/src/planner/examples/ExampleLibrary.tsx +189 -0
  202. package/src/planner/examples/ExamplePreviewBanner.tsx +55 -0
  203. package/src/planner/examples/ExamplesPage.tsx +25 -0
  204. package/src/planner/examples/OpenExampleButton.tsx +61 -0
  205. package/src/planner/examples/buildAggressiveSaver.ts +102 -0
  206. package/src/planner/examples/buildAnnuityEstate.ts +137 -0
  207. package/src/planner/examples/buildBaristaFire.ts +115 -0
  208. package/src/planner/examples/buildBracketFillRoth.ts +65 -0
  209. package/src/planner/examples/buildBridgeEarlyRetirement.ts +94 -0
  210. package/src/planner/examples/buildBrokerageNoHsa.ts +109 -0
  211. package/src/planner/examples/buildCoastFire.ts +88 -0
  212. package/src/planner/examples/buildContext.ts +20 -0
  213. package/src/planner/examples/buildEarlyCareerMatch.ts +93 -0
  214. package/src/planner/examples/buildEarlyRetireeAca.ts +61 -0
  215. package/src/planner/examples/buildExampleCouple.ts +103 -0
  216. package/src/planner/examples/buildFixedTargetSpending.ts +74 -0
  217. package/src/planner/examples/buildGlidepathAllocation.ts +131 -0
  218. package/src/planner/examples/buildGuardrailsFlex.ts +120 -0
  219. package/src/planner/examples/buildHsaPropertyDepth.ts +109 -0
  220. package/src/planner/examples/buildHsaStealthRetirement.ts +97 -0
  221. package/src/planner/examples/buildLeanFatFire.ts +109 -0
  222. package/src/planner/examples/buildLtcShock.ts +62 -0
  223. package/src/planner/examples/buildMovingStateTax.ts +53 -0
  224. package/src/planner/examples/buildNoAnnuityBrokerage.ts +92 -0
  225. package/src/planner/examples/buildRmdIrmaa.ts +55 -0
  226. package/src/planner/examples/buildSalaryGrowthEscalation.ts +96 -0
  227. package/src/planner/examples/buildStaticAllocationControl.ts +96 -0
  228. package/src/planner/examples/buildSurvivorYears.ts +62 -0
  229. package/src/planner/examples/buildUnderSavedSingle.ts +51 -0
  230. package/src/planner/examples/exampleCopy.ts +23 -0
  231. package/src/planner/examples/loadExample.ts +90 -0
  232. package/src/planner/examples/registry.ts +313 -0
  233. package/src/planner/explainPanels.tsx +233 -0
  234. package/src/planner/fields.tsx +381 -0
  235. package/src/planner/format.ts +33 -0
  236. package/src/planner/home/DataAndPrivacyCard.tsx +56 -0
  237. package/src/planner/home/GettingStartedPaths.tsx +46 -0
  238. package/src/planner/home/GettingStartedReopener.tsx +32 -0
  239. package/src/planner/home/StartHereLinks.tsx +22 -0
  240. package/src/planner/home/WelcomeHero.tsx +39 -0
  241. package/src/planner/home/YourPlans.tsx +72 -0
  242. package/src/planner/home/importErrorMessage.ts +22 -0
  243. package/src/planner/home/startHereSlugs.ts +7 -0
  244. package/src/planner/home/useHomeData.ts +190 -0
  245. package/src/planner/home/useHomeMode.ts +47 -0
  246. package/src/planner/householdActions.ts +22 -0
  247. package/src/planner/insights/InsightCardView.tsx +340 -0
  248. package/src/planner/insights/InsightsPage.tsx +204 -0
  249. package/src/planner/insights/categoryLabels.ts +11 -0
  250. package/src/planner/learnLinks.ts +85 -0
  251. package/src/planner/marketModelPicker.ts +172 -0
  252. package/src/planner/optimizePageChart.ts +40 -0
  253. package/src/planner/optimizePageClaim.ts +64 -0
  254. package/src/planner/planCompleteness.ts +27 -0
  255. package/src/planner/planContextCore.ts +26 -0
  256. package/src/planner/planner.css +2304 -0
  257. package/src/planner/provenanceLinks.ts +25 -0
  258. package/src/planner/sections/AccountFields.tsx +872 -0
  259. package/src/planner/sections/AccountsSection.tsx +89 -0
  260. package/src/planner/sections/AllocationPanel.tsx +261 -0
  261. package/src/planner/sections/AssumptionsSection.tsx +256 -0
  262. package/src/planner/sections/HouseholdSection.tsx +243 -0
  263. package/src/planner/sections/IncomeFloorSection.tsx +418 -0
  264. package/src/planner/sections/IncomeSection.tsx +170 -0
  265. package/src/planner/sections/InsuranceSection.tsx +362 -0
  266. package/src/planner/sections/SpendingSection.tsx +904 -0
  267. package/src/planner/sections/StrategySection.tsx +349 -0
  268. package/src/planner/sections/UpdateBalancesPanel.tsx +182 -0
  269. package/src/planner/sections/sectionHelpers.ts +48 -0
  270. package/src/planner/sections/shared.tsx +15 -0
  271. package/src/planner/sections.tsx +15 -0
  272. package/src/planner/ssAnalysis.ts +325 -0
  273. package/src/planner/successBand.ts +20 -0
  274. package/src/planner/survivorAnalysis.ts +277 -0
  275. package/src/planner/usStates.ts +19 -0
  276. package/src/planner/useMcSuccessRate.ts +77 -0
  277. package/src/planner/useProjection.ts +63 -0
  278. package/src/relocation/messages.ts +21 -0
  279. package/src/relocation/relocation.worker.ts +18 -0
  280. package/src/relocation/runRelocation.ts +17 -0
  281. package/src/relocation/runner.ts +22 -0
  282. package/src/report/brandingContext.ts +15 -0
  283. package/src/report/downloadReport.ts +34 -0
  284. package/src/report/reportHtml.ts +547 -0
  285. package/src/routes/LearnRoutes.tsx +46 -0
  286. package/src/routes/PlanRoutes.tsx +55 -0
  287. package/src/routes/RouteFallback.tsx +9 -0
  288. package/src/socialSecurity/breakEven.ts +107 -0
  289. package/src/socialSecurity/expectedPv.ts +164 -0
  290. package/src/socialSecurity/explain.ts +92 -0
  291. package/src/socialSecurity/ficaReturn.ts +81 -0
  292. package/src/socialSecurity/persistedSsGuard.ts +138 -0
  293. package/src/socialSecurity/ssFormUtils.ts +48 -0
  294. package/src/socialSecurity/ssaStatementXml.ts +156 -0
  295. package/src/socialSecurity/storage.ts +69 -0
  296. package/src/socialSecurity/survivorSwitching.ts +153 -0
  297. package/src/testSupport/samplePlan.ts +2 -0
  298. package/src/workers/run.ts +45 -0
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # @retiregolden/planner-ui
2
+
3
+ The [RetireGolden](https://retiregolden.app/) planner React UI — the complete
4
+ planner a host application composes inside its own router: plan picker and
5
+ workspace, results/Monte Carlo/scenarios/survivor/relocation pages, the
6
+ Roth & Tax / spending / Social Security optimizers, the import wizard, the
7
+ Learning Center, report export, and browser persistence (IndexedDB +
8
+ localStorage). All money math lives in
9
+ [`@retiregolden/engine`](https://www.npmjs.com/package/@retiregolden/engine)
10
+ (a dependency of this package, not re-exported by it); this package is the UI
11
+ and persistence around it.
12
+
13
+ Source of truth: [github.com/RetireGolden/RetireGolden](https://github.com/RetireGolden/RetireGolden)
14
+ (`packages/planner-ui`). Engineering docs live in the repo's `DOCS/`.
15
+
16
+ ## What ships, and the bundler contract
17
+
18
+ This package publishes its **TypeScript source** (`src/`, referenced directly
19
+ by `exports` and `types`) and requires a **Vite-class bundler** — it is not
20
+ consumable from plain Node or from bundlers that don't implement Vite
21
+ semantics. The planner tree relies on features only a Vite-style build
22
+ provides:
23
+
24
+ - `new Worker(new URL('./x.worker.ts', import.meta.url), { type: 'module' })`
25
+ for the Monte Carlo pool, the optimizers, and relocation compare;
26
+ - `import wasmUrl from 'highs/runtime?url'` for the HiGHS LP-solver wasm;
27
+ - `import.meta.glob`, `import.meta.env.DEV`, CSS and image imports.
28
+
29
+ Compiling to plain ESM would not remove any of those requirements, so the
30
+ package doesn't pretend otherwise. Known-good consumers: Vite ≥ 8 and
31
+ electron-vite, with React 19 and react-router v7 (peers).
32
+
33
+ ### Required Vite config
34
+
35
+ Dependency pre-bundling must skip this package (esbuild/rolldown pre-bundles
36
+ break the Vite-only constructs above) while still pre-bundling its
37
+ CommonJS-flavoured dependencies for dev:
38
+
39
+ ```ts
40
+ // vite.config.ts
41
+ export default defineConfig({
42
+ plugins: [react()],
43
+ optimizeDeps: {
44
+ exclude: ['@retiregolden/planner-ui'],
45
+ include: ['@retiregolden/planner-ui > highs', '@retiregolden/planner-ui > recharts'],
46
+ },
47
+ })
48
+ ```
49
+
50
+ Production builds work without this; dev mode does not — leave it in place.
51
+ TypeScript consumers should use `"moduleResolution": "bundler"` and include
52
+ `vite/client` types (standard in Vite templates), since type checking reads
53
+ this package's source.
54
+
55
+ ## Usage
56
+
57
+ ```bash
58
+ npm install @retiregolden/planner-ui react react-dom react-router-dom
59
+ ```
60
+
61
+ The host owns the router and mounts the planner under it:
62
+
63
+ ```tsx
64
+ import { StrictMode } from 'react'
65
+ import { createRoot } from 'react-dom/client'
66
+ import { BrowserRouter } from 'react-router-dom'
67
+ import '@retiregolden/planner-ui/index.css'
68
+ import { PlannerApp } from '@retiregolden/planner-ui'
69
+
70
+ createRoot(document.getElementById('root')!).render(
71
+ <StrictMode>
72
+ <BrowserRouter>
73
+ <PlannerApp />
74
+ </BrowserRouter>
75
+ </StrictMode>,
76
+ )
77
+ ```
78
+
79
+ `<PlannerApp />` is everything inside the router: the app chrome (header,
80
+ nav, theme toggle, footer), the route table, and the route error boundary.
81
+ Use `BrowserRouter`, `HashRouter` (Electron `file://` hosts), or
82
+ `MemoryRouter` as the host requires; a `basename` on the host's router is
83
+ respected because the planner only uses relative react-router APIs.
84
+
85
+ ### CSS and theming
86
+
87
+ - `@retiregolden/planner-ui/index.css` — required: the design-token layer
88
+ (CSS custom properties for both light and dark themes) plus base styles.
89
+ Import it once in the host.
90
+ - Component styles (`planner.css`, `learn.css`) are imported by the
91
+ components themselves; no extra import is needed.
92
+
93
+ Theming hook: the token layer is plain CSS custom properties on `:root` (and
94
+ `[data-theme]` variants), so a host can re-brand by loading its own
95
+ stylesheet after `index.css` and overriding tokens. Keep overrides
96
+ WCAG-AA-honest — the upstream palette is contrast-guarded by tests.
97
+
98
+ ### Host-served static assets (required)
99
+
100
+ The planner references a small set of **root-absolute URLs that the host must
101
+ serve** — they are deliberately not bundled, because several are
102
+ host-specific by nature. `app/public/` in the upstream repo is the reference
103
+ tree to copy from:
104
+
105
+ | Path | Used by | Notes |
106
+ |------|---------|-------|
107
+ | `/favicon.svg` | Header brand mark (mobile), logo fallback | |
108
+ | `/brand/retiregolden-logo-lockup.png`, `/brand/retiregolden-logo-lockup-light.png` | Header logo (dark/light) | Missing files degrade to `/favicon.svg` |
109
+ | `/learn/images/*.webp` | Learning Center article illustrations (~5 MB total) | Copy from `app/public/learn/images/` |
110
+ | `/THIRD-PARTY-NOTICES.txt` | Disclaimer page's third-party attribution link | **Must describe the host's own bundle** — generate it from your dependency tree (the upstream generator is `app/scripts/generate-third-party-notices.mjs`); do not copy the web app's file verbatim |
111
+
112
+ Electron hosts loading over `file://` should use `HashRouter` and a protocol
113
+ handler (or serve the app over a local scheme) so these root-absolute URLs
114
+ resolve against the app bundle, not the filesystem root.
115
+
116
+ ### Published API surface
117
+
118
+ The supported product API is the root export (`PlannerApp`) plus
119
+ `./index.css`. The exports map also exposes wildcard `./*.ts` subpaths
120
+ (e.g. `./report/reportHtml`) — these exist for the upstream repo's own test
121
+ and case-runner harnesses, are not covered by any stability promise, and may
122
+ move or change in any release. If a host needs one of them long-term, open an
123
+ upstream issue so it can be promoted to a real export instead.
124
+
125
+ ### Report branding
126
+
127
+ Downloaded HTML reports (Results, Report, and Optimizer pages) can carry the
128
+ host's identity via the optional `reportBranding` prop — a generic hook, with
129
+ RetireGolden defaults when omitted:
130
+
131
+ ```tsx
132
+ <PlannerApp
133
+ reportBranding={{
134
+ productName: 'Acme Wealth Planner', // report title, header line, filename
135
+ logoDataUri: 'data:image/png;base64,...', // letterhead logo (data: URI only — reports stay self-contained)
136
+ logoAlt: 'Acme Wealth',
137
+ accentColor: '#1a3a5c', // letterhead rule color
138
+ footerNote: 'Prepared by Acme Wealth Advisors LLC.',
139
+ }}
140
+ />
141
+ ```
142
+
143
+ `buildStandaloneReportHtml` (deep subpath `./report/reportHtml` — the
144
+ stability caveat under "Published API surface" applies) accepts the same
145
+ `branding` object directly for hosts that generate reports outside the
146
+ planner pages. Values are sanitized — the logo must be a base64
147
+ `data:image/...` URI, the accent must parse as a real CSS color (hex,
148
+ `rgb()`/`hsl()`, or a named color; anything else falls back to the default
149
+ gold), and text fields are escaped-and-kept — so the report's no-script
150
+ guarantee holds regardless of input. This branding
151
+ applies to downloaded reports only; the in-app chrome is themed via the CSS
152
+ tokens (above).
153
+
154
+ ### Storage
155
+
156
+ Plans persist in the browser profile via IndexedDB (`idb`) with localStorage
157
+ for small preferences, exactly as on retiregolden.app. Hosts that need a
158
+ different persistence story should treat that as an upstream conversation,
159
+ not a fork point.
160
+
161
+ ## Relationship to the web app
162
+
163
+ `app/` in the upstream repo is the reference host: it adds the PWA service
164
+ worker, SEO/meta, sitemap and prerender tooling, and static-hosting config,
165
+ then mounts `<PlannerApp />` exactly as in the snippet above. This package
166
+ contains no host-specific behavior — composition points are generic React
167
+ props/slots and CSS tokens, nothing keyed to any particular product.
168
+
169
+ ## License
170
+
171
+ **AGPL-3.0-only** (see [LICENSE](LICENSE)). The planner UI is free and
172
+ un-gutted — the full feature set ships in the free web app.
173
+
174
+ RetireGolden, LLC also ships a commercial desktop edition built from this
175
+ same UI under a separate commercial license, which funds the free one. That
176
+ dual-license arrangement is why contributions to the
177
+ [upstream repo](https://github.com/RetireGolden/RetireGolden) require a
178
+ one-time [Contributor License Agreement](https://github.com/RetireGolden/RetireGolden/blob/main/CLA.md)
179
+ — you keep your copyright; the CLA lets the LLC also ship your contribution
180
+ in the commercial edition. See
181
+ [CONTRIBUTING.md](https://github.com/RetireGolden/RetireGolden/blob/main/CONTRIBUTING.md).
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@retiregolden/planner-ui",
3
+ "version": "0.1.0",
4
+ "description": "The RetireGolden planner React UI: plan picker, planner workspace, results/Monte Carlo/optimizer pages, import wizard, Learning Center, and report export — the full planner tree a host app composes inside its own router. Ships TypeScript source and requires a Vite-class bundler.",
5
+ "license": "AGPL-3.0-only",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/RetireGolden/RetireGolden.git",
13
+ "directory": "packages/planner-ui"
14
+ },
15
+ "homepage": "https://github.com/RetireGolden/RetireGolden/tree/main/packages/planner-ui#readme",
16
+ "bugs": "https://github.com/RetireGolden/RetireGolden/issues",
17
+ "keywords": [
18
+ "retirement",
19
+ "financial-planning",
20
+ "react",
21
+ "planner",
22
+ "monte-carlo"
23
+ ],
24
+ "files": [
25
+ "src",
26
+ "!src/**/*.test.ts",
27
+ "!src/**/*.test.tsx",
28
+ "LICENSE",
29
+ "README.md"
30
+ ],
31
+ "types": "./src/index.ts",
32
+ "exports": {
33
+ ".": "./src/index.ts",
34
+ "./index.css": "./src/index.css",
35
+ "./*": "./src/*.ts",
36
+ "./package.json": "./package.json"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -b",
40
+ "lint": "eslint .",
41
+ "test": "vitest run",
42
+ "test:coverage": "vitest run --coverage"
43
+ },
44
+ "dependencies": {
45
+ "@retiregolden/engine": "^0.1.0",
46
+ "highs": "^1.14.2",
47
+ "idb": "^8.0.3",
48
+ "recharts": "^3.8.1"
49
+ },
50
+ "peerDependencies": {
51
+ "react": "^19.2.0",
52
+ "react-dom": "^19.2.0",
53
+ "react-router-dom": "^7.18.0"
54
+ },
55
+ "devDependencies": {
56
+ "@eslint/js": "^10.0.1",
57
+ "@types/jsdom": "^28.0.3",
58
+ "@types/node": "^24.12.3",
59
+ "@types/react": "^19.2.14",
60
+ "@types/react-dom": "^19.2.3",
61
+ "@vitejs/plugin-react": "^6.0.1",
62
+ "@vitest/coverage-v8": "^4.1.6",
63
+ "eslint": "^10.3.0",
64
+ "eslint-plugin-react-hooks": "^7.1.1",
65
+ "eslint-plugin-react-refresh": "^0.5.2",
66
+ "fake-indexeddb": "^6.2.5",
67
+ "globals": "^17.6.0",
68
+ "jsdom": "^29.1.1",
69
+ "react": "^19.2.6",
70
+ "react-dom": "^19.2.6",
71
+ "react-router-dom": "^7.18.0",
72
+ "typescript": "~6.0.2",
73
+ "typescript-eslint": "^8.59.2",
74
+ "vite": "^8.0.16",
75
+ "vitest": "^4.0.16"
76
+ }
77
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,246 @@
1
+ import { lazy, Suspense, useEffect, useRef, useState } from 'react'
2
+ import { Navigate, NavLink, Route, Routes, useLocation } from 'react-router-dom'
3
+ import { PlanPickerPage } from './planner/PlanPickerPage'
4
+ import { DisclaimerPage } from './planner/DisclaimerPage'
5
+ import { RouteErrorBoundary } from './RouteErrorBoundary.tsx'
6
+ import { RouteFallback } from './routes/RouteFallback'
7
+ import { readLocal, STORAGE_KEYS, writeLocal } from './data/localStore'
8
+ import { ReportBrandingContext } from './report/brandingContext'
9
+ import type { ReportBranding } from './report/reportHtml'
10
+ import './planner/planner.css'
11
+
12
+ const PlanRoutes = lazy(() => import('./routes/PlanRoutes'))
13
+ const LearnRoutes = lazy(() => import('./routes/LearnRoutes'))
14
+ // Lazy like /plan and /learn: Examples pulls in all example builders + the Zod
15
+ // schema, Compare pulls in the whole engine via projectPlan — eager imports
16
+ // here would drag both into the landing entry chunk.
17
+ const ExamplesPage = lazy(() => import('./planner/examples/ExamplesPage').then((m) => ({ default: m.ExamplesPage })))
18
+ const ComparePlansPage = lazy(() => import('./planner/ComparePlansPage').then((m) => ({ default: m.ComparePlansPage })))
19
+ // Lazy so the glob-derived test-suite manifests it embeds stay out of the landing chunk.
20
+ const HowTestedPage = lazy(() => import('./planner/HowTestedPage').then((m) => ({ default: m.HowTestedPage })))
21
+ // Lazy: the import wizard pulls in the per-source mappers and the Zod schema.
22
+ const ImportPage = lazy(() => import('./import/ImportPage').then((m) => ({ default: m.ImportPage })))
23
+
24
+ const navClass = ({ isActive }: { isActive: boolean }) =>
25
+ isActive ? 'nav-link nav-link--active' : 'nav-link'
26
+
27
+ /** Tab/history titles for non-plan routes; plan routes are owned by PlanWorkspace. */
28
+ const ROUTE_TITLES: ReadonlyArray<[prefix: string, title: string]> = [
29
+ ['/examples', 'Examples'],
30
+ ['/import', 'Import & migrate'],
31
+ ['/compare', 'Compare plans'],
32
+ ['/learn', 'Learn'],
33
+ ['/disclaimer', 'Disclaimer'],
34
+ ['/how-tested', 'How RetireGolden is tested'],
35
+ ]
36
+
37
+ function routeTitleOf(pathname: string): string | null {
38
+ if (pathname === '/') return 'Your plans'
39
+ for (const [prefix, title] of ROUTE_TITLES) {
40
+ if (pathname === prefix || pathname.startsWith(`${prefix}/`)) return title
41
+ }
42
+ return null
43
+ }
44
+
45
+ type ThemeMode = 'light' | 'dark' | 'system'
46
+
47
+ const THEME_STORAGE_KEY = STORAGE_KEYS.theme
48
+ const THEME_MODES: ThemeMode[] = ['light', 'dark', 'system']
49
+
50
+ function getInitialThemeMode(): ThemeMode {
51
+ if (typeof window === 'undefined') return 'system'
52
+ const stored = readLocal(THEME_STORAGE_KEY)
53
+ return stored === 'light' || stored === 'dark' || stored === 'system' ? stored : 'system'
54
+ }
55
+
56
+ function getResolvedTheme(mode: ThemeMode) {
57
+ if (mode !== 'system') return mode
58
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return 'light'
59
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
60
+ }
61
+
62
+ export interface PlannerAppProps {
63
+ /**
64
+ * Identity applied to downloaded HTML reports (name, logo, accent color,
65
+ * footer note) — a generic host hook; omit it and reports keep the
66
+ * RetireGolden defaults. In-app chrome is themed via CSS tokens instead
67
+ * (override the custom properties from index.css).
68
+ */
69
+ reportBranding?: ReportBranding
70
+ }
71
+
72
+ export function App({ reportBranding }: PlannerAppProps = {}) {
73
+ const location = useLocation()
74
+ const isLanding = location.pathname === '/' || location.pathname === '/examples'
75
+ const [themeMode, setThemeMode] = useState<ThemeMode>(getInitialThemeMode)
76
+ const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(() => getResolvedTheme(getInitialThemeMode()))
77
+ const [logoLoadFailed, setLogoLoadFailed] = useState(false)
78
+ const isFirstRoute = useRef(true)
79
+
80
+ // Page identity: retitle the tab per route (plan routes retitle themselves in
81
+ // PlanWorkspace with the plan name) and move focus to the main landmark on
82
+ // SPA navigation so screen readers hear the new page instead of silence.
83
+ useEffect(() => {
84
+ if (!location.pathname.startsWith('/plan/')) {
85
+ const title = routeTitleOf(location.pathname)
86
+ document.title = title ? `${title} · RetireGolden` : 'RetireGolden'
87
+ }
88
+ if (isFirstRoute.current) {
89
+ // Initial load: the browser's own focus/scroll behavior is correct.
90
+ isFirstRoute.current = false
91
+ return
92
+ }
93
+ document.getElementById('main-content')?.focus()
94
+ }, [location.pathname])
95
+
96
+ useEffect(() => {
97
+ const root = document.documentElement
98
+ const themeColor = document.querySelector<HTMLMetaElement>('meta[name="theme-color"]')
99
+
100
+ const applyTheme = () => {
101
+ const nextResolvedTheme = getResolvedTheme(themeMode)
102
+
103
+ root.dataset.theme = themeMode
104
+ setResolvedTheme(nextResolvedTheme)
105
+ writeLocal(THEME_STORAGE_KEY, themeMode)
106
+ themeColor?.setAttribute('content', nextResolvedTheme === 'dark' ? '#0e1116' : '#f4f6f8')
107
+ }
108
+
109
+ applyTheme()
110
+
111
+ if (themeMode !== 'system' || typeof window.matchMedia !== 'function') return undefined
112
+
113
+ const media = window.matchMedia('(prefers-color-scheme: dark)')
114
+ media.addEventListener('change', applyTheme)
115
+ return () => media.removeEventListener('change', applyTheme)
116
+ }, [themeMode])
117
+
118
+ const logoSrc = logoLoadFailed
119
+ ? '/favicon.svg'
120
+ : resolvedTheme === 'dark'
121
+ ? '/brand/retiregolden-logo-lockup.png'
122
+ : '/brand/retiregolden-logo-lockup-light.png'
123
+
124
+ return (
125
+ <ReportBrandingContext.Provider value={reportBranding ?? null}>
126
+ <div className={`app-shell planner-shell${isLanding ? ' app-shell--landing' : ''}`}>
127
+ <a className="skip-link" href="#main-content">
128
+ Skip to content
129
+ </a>
130
+ <header className="app-header">
131
+ <NavLink to="/" className="brand brand-logo-link" end aria-label="RetireGolden home">
132
+ <picture>
133
+ {/* Phones get the compact square mark; it's theme-independent and
134
+ skips loading the lockup PNG on mobile. */}
135
+ <source media="(max-width: 640px)" srcSet="/favicon.svg" />
136
+ <img
137
+ className="brand-logo"
138
+ src={logoSrc}
139
+ alt="RetireGolden"
140
+ onError={() => setLogoLoadFailed(true)}
141
+ />
142
+ </picture>
143
+ </NavLink>
144
+ {/* No hamburger at any width (owner preference): on narrow screens the
145
+ nav shares the logo row and the theme switcher wraps below, keeping
146
+ visual order = DOM order = tab order. */}
147
+ <div className="header-menu" id="header-menu">
148
+ <nav className="nav" aria-label="Primary">
149
+ <NavLink to="/" className={navClass} end>
150
+ Planner
151
+ </NavLink>
152
+ <NavLink to="/examples" className={navClass}>
153
+ Examples
154
+ </NavLink>
155
+ <NavLink to="/learn" className={navClass}>
156
+ Learn
157
+ </NavLink>
158
+ <NavLink to="/disclaimer" className={navClass}>
159
+ Disclaimer
160
+ </NavLink>
161
+ </nav>
162
+ <div className="theme-switcher" role="group" aria-label="Color theme">
163
+ {THEME_MODES.map((mode) => (
164
+ <button
165
+ key={mode}
166
+ className="theme-switcher-button"
167
+ type="button"
168
+ aria-pressed={themeMode === mode}
169
+ onClick={() => setThemeMode(mode)}
170
+ >
171
+ {mode[0].toUpperCase() + mode.slice(1)}
172
+ </button>
173
+ ))}
174
+ </div>
175
+ </div>
176
+ </header>
177
+ <main className="app-main" id="main-content" tabIndex={-1}>
178
+ <RouteErrorBoundary>
179
+ <Routes>
180
+ <Route path="/" element={<PlanPickerPage />} />
181
+ <Route
182
+ path="/examples"
183
+ element={
184
+ <Suspense fallback={<RouteFallback />}>
185
+ <ExamplesPage />
186
+ </Suspense>
187
+ }
188
+ />
189
+ <Route
190
+ path="/compare"
191
+ element={
192
+ <Suspense fallback={<RouteFallback />}>
193
+ <ComparePlansPage />
194
+ </Suspense>
195
+ }
196
+ />
197
+ <Route
198
+ path="/plan/*"
199
+ element={
200
+ <Suspense fallback={<RouteFallback />}>
201
+ <PlanRoutes />
202
+ </Suspense>
203
+ }
204
+ />
205
+ <Route
206
+ path="/learn/*"
207
+ element={
208
+ <Suspense fallback={<RouteFallback />}>
209
+ <LearnRoutes />
210
+ </Suspense>
211
+ }
212
+ />
213
+ <Route
214
+ path="/import"
215
+ element={
216
+ <Suspense fallback={<RouteFallback />}>
217
+ <ImportPage />
218
+ </Suspense>
219
+ }
220
+ />
221
+ <Route path="/disclaimer" element={<DisclaimerPage />} />
222
+ <Route
223
+ path="/how-tested"
224
+ element={
225
+ <Suspense fallback={<RouteFallback />}>
226
+ <HowTestedPage />
227
+ </Suspense>
228
+ }
229
+ />
230
+ {/* Retired v1 routes now redirect into the planner. */}
231
+ <Route path="/legacy" element={<Navigate to="/" replace />} />
232
+ <Route path="/longevity" element={<Navigate to="/" replace />} />
233
+ <Route path="/social-security" element={<Navigate to="/" replace />} />
234
+ </Routes>
235
+ </RouteErrorBoundary>
236
+ </main>
237
+ <footer className="app-footer">
238
+ <span className="muted small">
239
+ Educational only — not tax, legal, financial, or medical advice. All data stays on this device.{' '}
240
+ <NavLink to="/disclaimer">Full disclaimer</NavLink>
241
+ </span>
242
+ </footer>
243
+ </div>
244
+ </ReportBrandingContext.Provider>
245
+ )
246
+ }
@@ -0,0 +1,45 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react'
2
+ import { Link } from 'react-router-dom'
3
+
4
+ interface Props {
5
+ children: ReactNode
6
+ }
7
+
8
+ interface State {
9
+ error: Error | null
10
+ }
11
+
12
+ export class RouteErrorBoundary extends Component<Props, State> {
13
+ state: State = { error: null }
14
+
15
+ static getDerivedStateFromError(error: Error): State {
16
+ return { error }
17
+ }
18
+
19
+ componentDidCatch(error: Error, info: ErrorInfo): void {
20
+ console.error('Route render error:', error, info.componentStack)
21
+ }
22
+
23
+ render(): ReactNode {
24
+ if (this.state.error) {
25
+ return (
26
+ <div className="error-boundary-fallback" role="alert">
27
+ <h1>Something went wrong</h1>
28
+ <p className="muted">
29
+ This page hit an unexpected error. You can try reloading, or go back to the home page.
30
+ </p>
31
+ <pre className="error-boundary-stack">{this.state.error.message}</pre>
32
+ <p>
33
+ <button type="button" className="btn btn-primary" onClick={() => window.location.reload()}>
34
+ Reload
35
+ </button>{' '}
36
+ <Link className="btn btn-secondary" to="/">
37
+ Home
38
+ </Link>
39
+ </p>
40
+ </div>
41
+ )
42
+ }
43
+ return this.props.children
44
+ }
45
+ }
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="77" height="47" fill="none" aria-labelledby="vite-logo-title" viewBox="0 0 77 47"><title id="vite-logo-title">Vite</title><style>.parenthesis{fill:#000}@media (prefers-color-scheme:dark){.parenthesis{fill:#fff}}</style><path fill="#9135ff" d="M40.151 45.71c-.663.844-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.493c-.92 0-1.457-1.04-.92-1.788l7.479-10.471c1.07-1.498 0-3.578-1.842-3.578H15.443c-.92 0-1.456-1.04-.92-1.788l9.696-13.576c.213-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.472c-1.07 1.497 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.087.89 1.83L40.153 45.712z"/><mask id="a" width="48" height="47" x="14" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M40.047 45.71c-.663.843-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.389c-.92 0-1.457-1.04-.92-1.788l7.479-10.472c1.07-1.497 0-3.578-1.842-3.578H15.34c-.92 0-1.456-1.04-.92-1.788l9.696-13.575c.213-.297.556-.474.92-.474H53.93c.92 0 1.456 1.04.92 1.788L47.37 13.03c-1.07 1.498 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.088.89 1.831L40.049 45.712z"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#eee6ff" rx="5.508" ry="14.704" transform="rotate(269.814 20.96 11.29)scale(-1 1)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#eee6ff" rx="10.399" ry="29.851" transform="rotate(89.814 -16.902 -8.275)scale(1 -1)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#8900ff" rx="5.508" ry="30.487" transform="rotate(89.814 -19.197 -7.127)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.928 4.177)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.738 5.52)scale(1 -1)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#eee6ff" rx="14.072" ry="22.078" transform="rotate(93.35 31.245 55.578)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx="14.592" cy="9.743" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(39.51 14.592 9.743)"/></g><g filter="url(#k)"><ellipse cx="61.728" cy="-5.321" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 61.728 -5.32)"/></g><g filter="url(#l)"><ellipse cx="55.618" cy="7.104" fill="#00c2ff" rx="5.971" ry="9.665" transform="rotate(37.892 55.618 7.104)"/></g><g filter="url(#m)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#n)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#o)"><ellipse cx="49.857" cy="30.678" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 49.857 30.678)"/></g><g filter="url(#p)"><ellipse cx="52.623" cy="33.171" fill="#00c2ff" rx="5.971" ry="15.297" transform="rotate(37.892 52.623 33.17)"/></g></g><path d="M6.919 0c-9.198 13.166-9.252 33.575 0 46.789h6.215c-9.25-13.214-9.196-33.623 0-46.789zm62.424 0h-6.215c9.198 13.166 9.252 33.575 0 46.789h6.215c9.25-13.214 9.196-33.623 0-46.789" class="parenthesis"/><defs><filter id="b" width="60.045" height="41.654" x="-5.564" y="16.92" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-40.407" y="-6.762" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-35.435" y="2.801" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-30.84" y="20.8" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-29.307" y="21.949" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="29.961" y="-17.13" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-13.43" y="-22.082" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="34.321" y="-37.644" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="38.847" y="-10.552" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="22.45" y="-1.645" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="32.919" y="11.36" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter></defs></svg>