xertica-ui 2.8.0 → 2.9.7

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 (286) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +1 -1
  3. package/bin/cli.ts +327 -87
  4. package/bin/language-config.ts +58 -18
  5. package/components/blocks/auth/AuthPageShell.tsx +63 -18
  6. package/components/blocks/auth/SocialLoginButtons.tsx +3 -3
  7. package/components/blocks/index.ts +1 -0
  8. package/components/blocks/marketing/CTASection.tsx +73 -0
  9. package/components/blocks/marketing/ComplianceBadgeRow.tsx +49 -0
  10. package/components/blocks/marketing/EvidenceTimeline.tsx +91 -0
  11. package/components/blocks/marketing/FeatureRow.tsx +78 -0
  12. package/components/blocks/marketing/Hero.tsx +125 -0
  13. package/components/blocks/marketing/HowItWorksSteps.tsx +90 -0
  14. package/components/blocks/marketing/LogoCloud.tsx +47 -0
  15. package/components/blocks/marketing/MarketingFooter.tsx +109 -0
  16. package/components/blocks/marketing/MarketingNavbar.tsx +192 -0
  17. package/components/blocks/marketing/PillBadge.tsx +96 -0
  18. package/components/blocks/marketing/SectionHeading.tsx +82 -0
  19. package/components/blocks/marketing/ServiceCardGrid.tsx +238 -0
  20. package/components/blocks/marketing/StatBand.tsx +99 -0
  21. package/components/blocks/marketing/TopAccentCards.tsx +91 -0
  22. package/components/blocks/marketing/index.ts +41 -0
  23. package/components/brand/index.ts +1 -0
  24. package/components/brand/isotype/Isotype.tsx +64 -0
  25. package/components/brand/isotype/IsotypeDiagonal.tsx +51 -0
  26. package/components/brand/isotype/IsotypeFrames.tsx +82 -0
  27. package/components/brand/isotype/IsotypeMini.tsx +65 -0
  28. package/components/brand/isotype/IsotypeTwist.tsx +68 -0
  29. package/components/brand/isotype/frame-variants.ts +20 -0
  30. package/components/brand/isotype/index.ts +9 -0
  31. package/components/brand/isotype/isotype-diagonal.mdx +52 -0
  32. package/components/brand/isotype/isotype-diagonal.stories.tsx +44 -0
  33. package/components/brand/isotype/isotype-diagonal.test.tsx +180 -0
  34. package/components/brand/isotype/isotype-frames.mdx +50 -0
  35. package/components/brand/isotype/isotype-frames.stories.tsx +63 -0
  36. package/components/brand/isotype/isotype-frames.test.tsx +92 -0
  37. package/components/brand/isotype/isotype-mini.mdx +61 -0
  38. package/components/brand/isotype/isotype-mini.stories.tsx +75 -0
  39. package/components/brand/isotype/isotype-mini.test.tsx +45 -0
  40. package/components/brand/isotype/isotype-twist.test.tsx +71 -0
  41. package/components/brand/isotype/isotype.mdx +61 -0
  42. package/components/brand/isotype/isotype.stories.tsx +74 -0
  43. package/components/brand/isotype/isotype.test.tsx +41 -0
  44. package/components/brand/isotype/mini-patterns.ts +50 -0
  45. package/components/brand/isotype/patterns.ts +83 -0
  46. package/components/brand/isotype/twist-variants.ts +20 -0
  47. package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +3 -6
  48. package/components/pages/index.ts +1 -0
  49. package/components/pages/landing-page/LandingPage.tsx +360 -0
  50. package/components/pages/landing-page/index.ts +1 -0
  51. package/components/pages/login-page/LoginPage.tsx +3 -6
  52. package/components/pages/reset-password-page/ResetPasswordPage.tsx +1 -4
  53. package/components/pages/verify-email-page/VerifyEmailPage.tsx +2 -5
  54. package/components/ui/button/button.test.tsx +1 -1
  55. package/components/ui/button/button.tsx +7 -6
  56. package/components/ui/card/card.test.tsx +12 -0
  57. package/components/ui/card/card.tsx +5 -3
  58. package/components/ui/input-otp/input-otp.test.tsx +14 -1
  59. package/contexts/BrandColorsContext.tsx +9 -5
  60. package/contexts/theme-data.ts +32 -5
  61. package/dist/AssistantChart-BU48lxL2.js +3383 -0
  62. package/dist/AssistantChart-CoN6ga6Z.js +3383 -0
  63. package/dist/AssistantChart-DIRYJ7WY.js +3383 -0
  64. package/dist/AssistantChart-DOnY1ZlD.cjs +3551 -0
  65. package/dist/AssistantChart-DjM3qnp5.cjs +3551 -0
  66. package/dist/AssistantChart-hxPyE1S9.cjs +3551 -0
  67. package/dist/AudioPlayer-CpihsbaD.js +1278 -0
  68. package/dist/AudioPlayer-CvGajzMC.cjs +1282 -0
  69. package/dist/AudioPlayer-CvHpFqDG.cjs +1282 -0
  70. package/dist/AudioPlayer-D4S7GQou.js +1278 -0
  71. package/dist/BrandColorsContext-BNKLUVcU.js +829 -0
  72. package/dist/BrandColorsContext-DNZGHItQ.cjs +836 -0
  73. package/dist/BrandColorsContext-Ug_TZ8yn.cjs +846 -0
  74. package/dist/BrandColorsContext-z0O2LlZz.js +839 -0
  75. package/dist/CodeBlock-BkaZf-R8.cjs +223 -0
  76. package/dist/CodeBlock-CKu8LgbP.cjs +223 -0
  77. package/dist/CodeBlock-DiLS5LLI.js +221 -0
  78. package/dist/CodeBlock-zdylBGdZ.js +221 -0
  79. package/dist/CustomTooltipContent-BAR5qCQg.js +33 -0
  80. package/dist/CustomTooltipContent-CBLHBxX8.cjs +54 -0
  81. package/dist/CustomTooltipContent-CjWm3jjV.js +33 -0
  82. package/dist/CustomTooltipContent-DTV9MN9E.cjs +54 -0
  83. package/dist/EvidenceTimeline-DrRk4yqb.cjs +560 -0
  84. package/dist/EvidenceTimeline-l4MaV4C7.js +546 -0
  85. package/dist/FeatureCard-CIbBHqeY.cjs +497 -0
  86. package/dist/FeatureCard-DtoKPSfW.cjs +497 -0
  87. package/dist/FeatureCard-P4iEpBmB.js +485 -0
  88. package/dist/FeatureCard-e3f8bnF0.js +485 -0
  89. package/dist/ImageWithFallback-C1ww9Hg3.cjs +122 -0
  90. package/dist/ImageWithFallback-CBrkAEo3.cjs +122 -0
  91. package/dist/ImageWithFallback-CGk5zPH-.js +98 -0
  92. package/dist/ImageWithFallback-CfnoFFSP.js +98 -0
  93. package/dist/Isotype--fRZWwS6.js +85 -0
  94. package/dist/Isotype-0dpLqetB.js +85 -0
  95. package/dist/Isotype-B8XLEXSB.cjs +92 -0
  96. package/dist/Isotype-ChKwN7e-.cjs +91 -0
  97. package/dist/IsotypeDiagonal-CuPYPP4f.js +84 -0
  98. package/dist/IsotypeDiagonal-eNcOXRAc.cjs +91 -0
  99. package/dist/IsotypeDiagonal-oiwpXZhE.js +104 -0
  100. package/dist/IsotypeDiagonal-xMFoFXkb.cjs +109 -0
  101. package/dist/IsotypeFrames-BcWtyAWp.cjs +83 -0
  102. package/dist/IsotypeFrames-BnvSJKiH.cjs +73 -0
  103. package/dist/IsotypeFrames-iDRTRuIu.js +79 -0
  104. package/dist/IsotypeFrames-qoXOvOJt.js +69 -0
  105. package/dist/IsotypeMini-CQtWVo3h.js +126 -0
  106. package/dist/IsotypeMini-Cu_sW5yN.cjs +134 -0
  107. package/dist/IsotypeTwist-BTdNL4iV.js +106 -0
  108. package/dist/IsotypeTwist-DvsUgZs_.cjs +115 -0
  109. package/dist/LanguageContext-B1aOXO-K.js +1987 -0
  110. package/dist/LanguageContext-BP0k7AzI.js +1993 -0
  111. package/dist/LanguageContext-CChcIHIG.cjs +1998 -0
  112. package/dist/LanguageContext-ubC7TSs_.cjs +1992 -0
  113. package/dist/LanguageSelector-BLFPXGo8.cjs +48 -0
  114. package/dist/LanguageSelector-CYRzWkRZ.cjs +48 -0
  115. package/dist/LanguageSelector-CyHjZgC4.cjs +48 -0
  116. package/dist/LanguageSelector-DE8lIySO.js +46 -0
  117. package/dist/LanguageSelector-DUr1hjBW.js +46 -0
  118. package/dist/LanguageSelector-DjbWIKWC.js +46 -0
  119. package/dist/LogoCloud-C_uMKVNg.cjs +513 -0
  120. package/dist/LogoCloud-DJsMPNPh.js +500 -0
  121. package/dist/MarketingFooter-B_ycEdTJ.cjs +727 -0
  122. package/dist/MarketingFooter-Bhx-2Vlr.cjs +727 -0
  123. package/dist/MarketingFooter-Br1MGK9e.js +699 -0
  124. package/dist/MarketingFooter-BwE1vmeX.cjs +700 -0
  125. package/dist/MarketingFooter-CsZ1_xL9.js +709 -0
  126. package/dist/MarketingFooter-DCwf78Q4.cjs +716 -0
  127. package/dist/MarketingFooter-KH2r-OOL.js +709 -0
  128. package/dist/MarketingFooter-nB7G7gQ5.js +682 -0
  129. package/dist/SectionHeading-DEeOKcAd.cjs +857 -0
  130. package/dist/SectionHeading-DMNObcwm.js +838 -0
  131. package/dist/ThemeToggle--CGJjQJv.cjs +219 -0
  132. package/dist/ThemeToggle-Bn_7Pmfe.js +215 -0
  133. package/dist/ThemeToggle-CoL8Ady7.js +215 -0
  134. package/dist/ThemeToggle-D2Wzm5bn.cjs +219 -0
  135. package/dist/VerifyEmailPage-90lK6pP4.js +3258 -0
  136. package/dist/VerifyEmailPage-BJX1Rter.cjs +3320 -0
  137. package/dist/VerifyEmailPage-BSWvOgry.js +3310 -0
  138. package/dist/VerifyEmailPage-CDqIkhx-.js +3224 -0
  139. package/dist/VerifyEmailPage-Crxje3ZB.js +3323 -0
  140. package/dist/VerifyEmailPage-D2ShbuEN.cjs +3333 -0
  141. package/dist/VerifyEmailPage-DEBZXQ7c.cjs +3310 -0
  142. package/dist/VerifyEmailPage-DGaX-jZv.cjs +3268 -0
  143. package/dist/VerifyEmailPage-ZtOPkgOA.cjs +3234 -0
  144. package/dist/VerifyEmailPage-s927GSiR.js +3300 -0
  145. package/dist/XerticaOrbe-BHex09xs.js +1925 -0
  146. package/dist/XerticaOrbe-BKW3LJEz.js +1925 -0
  147. package/dist/XerticaOrbe-Br2i99U_.cjs +1927 -0
  148. package/dist/XerticaOrbe-kjFmuyx-.cjs +1927 -0
  149. package/dist/XerticaProvider-Bi5lKC9N.js +44 -0
  150. package/dist/XerticaProvider-BrPMVhiv.js +44 -0
  151. package/dist/XerticaProvider-CnX5kt5W.cjs +46 -0
  152. package/dist/XerticaProvider-DEKNJTlN.js +44 -0
  153. package/dist/XerticaProvider-X_wavDYA.cjs +46 -0
  154. package/dist/XerticaProvider-b3n_oL8w.cjs +46 -0
  155. package/dist/alert-dialog--qdlmB5i.cjs +872 -0
  156. package/dist/alert-dialog-C48TOdYU.cjs +872 -0
  157. package/dist/alert-dialog-CEPpnpXt.js +819 -0
  158. package/dist/alert-dialog-NjflitDa.js +819 -0
  159. package/dist/assistant.cjs.js +2 -2
  160. package/dist/assistant.es.js +2 -2
  161. package/dist/avatar-CtEKgg7s.cjs +77 -0
  162. package/dist/avatar-D03MNQxs.js +54 -0
  163. package/dist/avatar-Df7KgSvs.js +54 -0
  164. package/dist/avatar-Gy0_Xu9i.cjs +77 -0
  165. package/dist/blocks.cjs.js +33 -18
  166. package/dist/blocks.es.js +2 -2
  167. package/dist/brand.cjs.js +110 -4
  168. package/dist/brand.es.js +98 -4
  169. package/dist/breadcrumb-BGO5PZPG.js +90 -0
  170. package/dist/breadcrumb-BvGAY4ne.cjs +98 -0
  171. package/dist/breadcrumb-CU-punra.js +90 -0
  172. package/dist/breadcrumb-DjV9wfX1.cjs +98 -0
  173. package/dist/button-B05czY_8.js +62 -0
  174. package/dist/button-B3SE1liJ.js +62 -0
  175. package/dist/button-BMDlWZR8.cjs +85 -0
  176. package/dist/button-DVhloDtr.cjs +85 -0
  177. package/dist/cli.js +280 -62
  178. package/dist/components/blocks/auth/AuthPageShell.d.ts +21 -5
  179. package/dist/components/blocks/index.d.ts +1 -0
  180. package/dist/components/blocks/marketing/CTASection.d.ts +28 -0
  181. package/dist/components/blocks/marketing/ComplianceBadgeRow.d.ts +23 -0
  182. package/dist/components/blocks/marketing/EvidenceTimeline.d.ts +26 -0
  183. package/dist/components/blocks/marketing/FeatureRow.d.ts +25 -0
  184. package/dist/components/blocks/marketing/Hero.d.ts +35 -0
  185. package/dist/components/blocks/marketing/HowItWorksSteps.d.ts +29 -0
  186. package/dist/components/blocks/marketing/LogoCloud.d.ts +22 -0
  187. package/dist/components/blocks/marketing/MarketingFooter.d.ts +42 -0
  188. package/dist/components/blocks/marketing/MarketingNavbar.d.ts +47 -0
  189. package/dist/components/blocks/marketing/PillBadge.d.ts +21 -0
  190. package/dist/components/blocks/marketing/SectionHeading.d.ts +23 -0
  191. package/dist/components/blocks/marketing/ServiceCardGrid.d.ts +65 -0
  192. package/dist/components/blocks/marketing/StatBand.d.ts +25 -0
  193. package/dist/components/blocks/marketing/TopAccentCards.d.ts +29 -0
  194. package/dist/components/blocks/marketing/index.d.ts +28 -0
  195. package/dist/components/brand/index.d.ts +1 -0
  196. package/dist/components/brand/isotype/Isotype.d.ts +27 -0
  197. package/dist/components/brand/isotype/IsotypeDiagonal.d.ts +19 -0
  198. package/dist/components/brand/isotype/IsotypeFrames.d.ts +21 -0
  199. package/dist/components/brand/isotype/IsotypeMini.d.ts +26 -0
  200. package/dist/components/brand/isotype/IsotypeTwist.d.ts +22 -0
  201. package/dist/components/brand/isotype/frame-variants.d.ts +11 -0
  202. package/dist/components/brand/isotype/index.d.ts +9 -0
  203. package/dist/components/brand/isotype/mini-patterns.d.ts +25 -0
  204. package/dist/components/brand/isotype/patterns.d.ts +42 -0
  205. package/dist/components/brand/isotype/twist-variants.d.ts +11 -0
  206. package/dist/components/pages/forgot-password-page/ForgotPasswordPage.d.ts +2 -2
  207. package/dist/components/pages/index.d.ts +1 -0
  208. package/dist/components/pages/landing-page/LandingPage.d.ts +18 -0
  209. package/dist/components/pages/landing-page/index.d.ts +1 -0
  210. package/dist/components/pages/login-page/LoginPage.d.ts +2 -2
  211. package/dist/components/pages/verify-email-page/VerifyEmailPage.d.ts +1 -1
  212. package/dist/components/ui/card/card.d.ts +3 -1
  213. package/dist/contexts/theme-data.d.ts +6 -0
  214. package/dist/dropdown-menu-ChQa9zW3.cjs +225 -0
  215. package/dist/dropdown-menu-DRvnsQAt.cjs +225 -0
  216. package/dist/dropdown-menu-DUk81i5Y.js +190 -0
  217. package/dist/dropdown-menu-NlUHhVmK.js +190 -0
  218. package/dist/hooks.cjs.js +2 -2
  219. package/dist/hooks.es.js +2 -2
  220. package/dist/index.cjs.js +57 -41
  221. package/dist/index.es.js +25 -25
  222. package/dist/input-Csp_Metb.cjs +91 -0
  223. package/dist/input-DVRwGVLx.js +65 -0
  224. package/dist/input-Dqyj7Hdw.cjs +91 -0
  225. package/dist/input-dOgrFQ-3.js +65 -0
  226. package/dist/layout.cjs.js +1 -1
  227. package/dist/layout.es.js +1 -1
  228. package/dist/media.cjs.js +1 -1
  229. package/dist/media.es.js +1 -1
  230. package/dist/pages.cjs.js +2 -1
  231. package/dist/pages.es.js +1 -1
  232. package/dist/rich-text-editor-7K7UjLJN.cjs +2968 -0
  233. package/dist/rich-text-editor-B3jLP2bm.cjs +2968 -0
  234. package/dist/rich-text-editor-BntMVQsB.js +2894 -0
  235. package/dist/rich-text-editor-Diz_onyB.js +2894 -0
  236. package/dist/rich-text-editor-RWvK4hd3.cjs +2968 -0
  237. package/dist/rich-text-editor-gauEkzPM.js +2894 -0
  238. package/dist/select-B6PygS38.cjs +161 -0
  239. package/dist/select-BD763J5Y.cjs +161 -0
  240. package/dist/select-QJDKZITN.js +130 -0
  241. package/dist/select-q7aDfuPn.js +130 -0
  242. package/dist/sidebar-Bkk1YAsD.cjs +1154 -0
  243. package/dist/sidebar-CuzYeOSz.cjs +1154 -0
  244. package/dist/sidebar-CwlW3LXE.js +1151 -0
  245. package/dist/sidebar-DNkf-Ug6.js +1151 -0
  246. package/dist/sidebar-DaS3fplw.cjs +1154 -0
  247. package/dist/sidebar-FDCY42oP.js +1151 -0
  248. package/dist/skeleton-BtBwJ39W.js +79 -0
  249. package/dist/skeleton-CJyg0eJ1.cjs +88 -0
  250. package/dist/skeleton-D8WaauK0.cjs +88 -0
  251. package/dist/skeleton-DBYtjaZW.cjs +88 -0
  252. package/dist/skeleton-DY_lSFWf.js +79 -0
  253. package/dist/skeleton-Df65Ox10.js +79 -0
  254. package/dist/slider-BKV_vvsh.cjs +78 -0
  255. package/dist/slider-B_lUjFNa.cjs +78 -0
  256. package/dist/slider-CtBpIaU4.js +56 -0
  257. package/dist/slider-KgWmSJbx.js +56 -0
  258. package/dist/tooltip-BPttuPxU.js +48 -0
  259. package/dist/tooltip-CIKRzQqE.cjs +72 -0
  260. package/dist/tooltip-CoDByjE_.cjs +72 -0
  261. package/dist/tooltip-_MAwsiOL.js +48 -0
  262. package/dist/ui.cjs.js +13 -13
  263. package/dist/ui.es.js +13 -13
  264. package/dist/xertica-assistant-B1xAl2Vn.js +2173 -0
  265. package/dist/xertica-assistant-BBUMaUXw.js +2173 -0
  266. package/dist/xertica-assistant-CWuBwfzt.cjs +2180 -0
  267. package/dist/xertica-assistant-D2hlwiEP.js +2173 -0
  268. package/dist/xertica-assistant-DN2J_sQQ.cjs +2180 -0
  269. package/dist/xertica-assistant-DdYS6WXT.cjs +2180 -0
  270. package/dist/xertica-ui.css +1 -1
  271. package/llms-compact.txt +1 -1
  272. package/llms.txt +1 -1
  273. package/package.json +4 -2
  274. package/styles/xertica/base.css +8 -0
  275. package/styles/xertica/tokens.css +1 -0
  276. package/templates/package.json +1 -1
  277. package/templates/src/app/components/AuthGuard.tsx +6 -0
  278. package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +1 -4
  279. package/templates/src/features/auth/ui/LoginContent.tsx +1 -4
  280. package/templates/src/features/auth/ui/ResetPasswordContent.tsx +1 -4
  281. package/templates/src/features/auth/ui/VerifyEmailContent.tsx +1 -4
  282. package/templates/src/locales/en/pages/landing.json +175 -0
  283. package/templates/src/locales/es/pages/landing.json +175 -0
  284. package/templates/src/locales/pt-BR/pages/landing.json +175 -0
  285. package/templates/src/pages/LandingPage.tsx +349 -0
  286. package/templates/src/styles/xertica/tokens.css +1 -0
package/CHANGELOG.md CHANGED
@@ -11,6 +11,114 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
12
  ---
13
13
 
14
+ ## [2.9.7] — 2026-07-14
15
+
16
+ ### Fixed
17
+
18
+ - **Suíte de testes com exceção não tratada bloqueando `npm publish`** — o pacote de terceiros `input-otp` agenda três `setTimeout` internos (0ms/10ms/50ms) em um `useEffect` sem função de limpeza no unmount. Ao rodar a suíte completa, o cleanup automático do Testing Library desmontava o `InputOTP` entre um teste e outro sem cancelar esses timers, que disparavam mais tarde — já fora do ambiente jsdom daquele arquivo — lançando `ReferenceError: window is not defined` como exceção não tratada. Isso fazia `vitest run` sair com erro mesmo com todos os 358 testes passando, derrubando a etapa `test:unit` do `prepublishOnly` e, com isso, qualquer tentativa de `npm publish`. Corrigido em `components/ui/input-otp/input-otp.test.tsx` usando `vi.useFakeTimers()`/`vi.useRealTimers()` ao redor de cada teste, impedindo que os timers da lib vazem para fora do ciclo de vida do teste.
19
+ - **`npm publish` alertava sobre correção automática do campo `bin`** — `"bin": "./dist/cli.js"` (string, com prefixo `./`) era normalizado pelo próprio npm no momento do publish, gerando o aviso `npm auto-corrected some errors in your package.json`. Corrigido escrevendo `bin` já na forma canônica esperada (`{ "xertica-ui": "dist/cli.js" }`), sem mudança de comportamento — apenas elimina o aviso.
20
+
21
+ ---
22
+
23
+ ## [2.9.6] — 2026-07-14
24
+
25
+ ### Added
26
+
27
+ - **CLI `init` pergunta o tipo de projeto antes de tudo** — novo primeiro prompt `O que você deseja criar?` com duas opções: `Full system` (o fluxo completo existente, inalterado: auth, sidebar, páginas, i18n) e `Web page` (um scaffold mínimo com apenas a landing page de marketing). Escolhendo `Web page`, o fluxo se reduz a 3 prompts — idiomas (multiselect), tema (select) e instalação de dependências (confirm) — e o projeto já sai pronto, sem nenhuma outra configuração.
28
+ - **Novo scaffold mínimo para "Web page"** (`initPageProject` em `bin/cli.ts`) — gera um `App.tsx` que renderiza `LandingPage` (via novo `generateLandingAppTsx` em `bin/language-config.ts`) diretamente dentro de `XerticaProvider`, sem `Router`, `QueryClientProvider` ou `AuthProvider`. Não cria `AuthContext.tsx`, `AuthGuard.tsx`, `AppLayout.tsx`, `src/shared/` nem `src/features/` — apenas `src/app/App.tsx`, `src/pages/LandingPage.tsx`, i18n/locales e tokens de tema. Também omite `CLAUDE.md`/`guidelines/` (documentam a arquitetura FSD de auth, inexistente neste scaffold) e `.env.example` (chaves usadas só pelo assistente/mapa).
29
+ - **`src/pages/LandingPage.tsx` do scaffold "Web page" ganha conteúdo de demonstração real** — em vez de um re-export de `xertica-ui/pages`, o template (`templates/src/pages/LandingPage.tsx`) agora traz a composição completa da landing page (Hero, cards, feature rows, "how it works", compliance/stats, CTA, footer) montada localmente a partir de `xertica-ui/blocks`/`xertica-ui/brand`, igual ao padrão já usado pelos demais templates (`HomeContent.tsx` etc.) — o usuário recebe algo editável como base, não uma caixa-preta importada da lib.
30
+ - **`.xertica.json` ganha o campo `projectType: 'page' | 'system'`** — projetos escaffoldados antes desta versão não têm o campo e são tratados como `'system'` em todos os pontos que o leem, preservando compatibilidade.
31
+
32
+ ### Changed
33
+
34
+ - **CLI `update` adaptado para os dois tipos de projeto** — em projetos `page`, o menu principal omite a opção `Assistant` (sem conceito de assistente nesse scaffold); as opções `Languages` e `Dark Mode` regeneram `App.tsx` via `generateLandingAppTsx` em vez de `generateAppTsx`; e `Project files` mostra uma lista reduzida de 2 itens (`App shell + landing page`, `Root config files`) em vez dos 5 itens do fluxo completo, evitando tentar atualizar `AppLayout.tsx`/`AuthGuard.tsx`/`src/shared/`/`src/features/`, que não existem nesse tipo de projeto.
35
+ - **`generateAppTsx` refatorado** — a lógica de montagem da prop `availableLanguages` foi extraída para um helper interno compartilhado (`buildAvailableLanguagesProp`), reaproveitado agora também pelo novo `generateLandingAppTsx`.
36
+
37
+ ---
38
+
39
+ ## [2.9.5] — 2026-07-14
40
+
41
+ ### Added
42
+
43
+ - **Novo isotipo `IsotypeTwist`** (`components/brand/isotype`) — quinta marca da série "isotipos": duas formas angulares sobrepostas, seguindo o mesmo mecanismo do `IsotypeFrames` (ambas 100% opacas, interseção resolvida via `mix-blend-mode: multiply` na forma da frente, com `isolate` na raiz), com duas variantes de cor registradas em `ISOTYPE_TWIST_VARIANTS`: `'magenta-blue'` (rosa + azul) e `'blue-red'` (azul + vermelho). Substitui o `IsotypeDiagonal` no painel esquerdo do `AuthPageShell` (`leftPanel="isotype"`, usado na página de login), agora renderizado com a variante `'magenta-blue'`.
44
+ - **`SectionHeading` ganha uma barra de destaque opcional** — nova prop `accentColor` (mesma convenção `BlockColor`/`colorTokens` local dos demais blocos) renderiza uma pequena barra colorida sob o título, aproximando o padrão da referência de mockup. É opcional: sem `accentColor`, o título continua exatamente como antes.
45
+
46
+ ### Changed
47
+
48
+ - **`SectionHeading` simplificado** — removidos o numeral fantasma (`index`) e o rótulo eyebrow, mantendo apenas o título (com barra de destaque e descrição opcionais), conforme a referência de mockup. As props `index`/`eyebrow` saem da API do componente; as chaves de tradução `ecosystem.eyebrow`, `useCases.eyebrow` e `methodology.eyebrow` (agora não usadas) foram removidas dos 6 arquivos `landing.json`. Aplicado às 3 seções que já usam `SectionHeading` na `LandingPage` (Ecossistema, Casos de uso, Metodologia).
49
+ - **`ServiceCardGrid`/`ServiceCard` com `icon` opcional** — o chip de ícone só renderiza quando `icon` é informado, permitindo cards sem ícone (título, descrição e checklist/bullets/CTA), conforme a referência de mockup. Aplicado à seção "Casos de uso" da `LandingPage`, que passa a renderizar os 3 cards sem ícone.
50
+
51
+ - **`AuthPageShell` (`leftPanel="isotype"`) com fundos invertidos** — o painel do isotipo na página de login passa a usar `bg-muted` (mais escuro) e o painel do formulário à direita passa a usar `bg-card` (Marfim, `#FFFEF8` no tema xertica, mais claro), invertendo a relação de claridade anterior entre os dois lados.
52
+ - **`ForgotPasswordPage`, `ResetPasswordPage` e `VerifyEmailPage` trocam a foto de fundo pelo painel de isotipo** — as 3 telas usavam `AuthPageShell` com `imageSrc`/foto de herói e um overlay de gradiente que, no tema xertica (token de gradiente plano em `#1E1E1E`), ficava com o painel esquerdo escuro. Passam a usar `leftPanel="isotype"`, a mesma solução (agora clara) já aplicada à `LoginPage`, unificando a aparência das 4 telas de autenticação. Espelhado em `templates/src/features/auth/ui/{ForgotPassword,ResetPassword,VerifyEmail}Content.tsx`.
53
+ - **`SectionHeading` perde a linha divisória superior** — removida a borda `border-t`/`pt-6` que antecedia o título (Ecossistema, Casos de uso, Metodologia na `LandingPage`); o cabeçalho agora fica sem regra visual antes do título.
54
+ - **`CTASection` volta a ter fundo escuro** — troca o `bg-card` plano por `bg-sidebar`/`border-sidebar-border` (mesma superfície escura já usada na seção de metodologia desta página) e o texto passa a `text-sidebar-foreground`/`text-sidebar-foreground/70`, sem gradiente e sem cor hardcoded. Os botões continuam com as variantes naturais (`default`/`outline`), que já contrastam corretamente sobre `bg-sidebar`.
55
+ - **Seção de compliance/stats da `LandingPage` reorganizada** — `ComplianceBadgeRow` (badges) e `StatBand` (números) agora ficam contidos em um único card (`border`, `bg-card`, `rounded-2xl`) com um divisor entre os dois grupos, em vez de flutuarem soltos direto sobre o fundo da página. `ComplianceBadgeRow` passa de um `flex flex-wrap` com gaps largos para um `grid grid-cols-2 sm:grid-cols-4`, alinhando os badges em colunas; o chip de ícone usa `bg-background` para manter contraste dentro do novo card.
56
+ - **`IsotypeFrames` restrito a duas variantes de cor** — `IsotypeFrameVariantId` passa de 6 para apenas `'blue-magenta'` e `'blue-yellow'` (azul+rosa e azul+amarelo); `'green-red'`, `'green-blue'`, `'magenta-yellow'` e `'magenta-purple'` saem do registro. Único uso afetado fora do próprio componente: a seção "Onboarding" da `LandingPage`, que usava `magenta-yellow` e passa a usar `blue-yellow`. No par `blue-yellow`, o azul é o quadro de trás (posicionado mais para cima) e o amarelo o quadro da frente (posicionado mais para baixo).
57
+
58
+ ### Removed
59
+
60
+ - **Removidas as seções de depoimento e de "pilares" da `LandingPage`** — o card de citação com `StatBand` (componentes/temas/downloads) e o grid de 4 colunas com ícones (tokens de tema, componentes acessíveis, blocos prontos, CLI de scaffolding) saem da página. As chaves de tradução `testimonial`, `pillars` e `stats.components`/`stats.themes`/`stats.downloads` (agora não usadas) foram removidas dos 6 arquivos `landing.json`.
61
+
62
+ ---
63
+
64
+ ## [2.9.4] — 2026-07-13
65
+
66
+ ### Added
67
+
68
+ - **Novo bloco `HowItWorksSteps`** (`xertica-ui/blocks` → `marketing`) — variante horizontal do `EvidenceTimeline`: mesma badge numerada/colorida (`colorTokens` local, sem linha conectora), em grid responsivo (`grid-cols-1 sm:grid-cols-2 lg:grid-cols-4`) em vez de lista vertical com fio. Substitui o `EvidenceTimeline` na seção "Metodologia" da `LandingPage` (mesmos 4 itens/cópias); o `EvidenceTimeline` continua exportado e funcional para quem preferir o layout vertical.
69
+ - **Novo bloco `TopAccentCards`** (`xertica-ui/blocks` → `marketing`) — par de cards com destaque de cor na borda superior (`border-t-4`), título, corpo e lista opcional de bullets simples, sem ícone/CTA. Segue a mesma convenção local de `BlockColor`/`colorTokens` dos demais blocos de marketing. Nova seção "framing" (propósito + problema) inserida na `LandingPage` logo após o `Hero`.
70
+
71
+ ### Changed
72
+
73
+ - **`CTASection` perde o fundo em gradiente** — passa a usar um fundo plano `bg-card` com borda (`border border-border`), título em `text-foreground` e descrição em `text-muted-foreground`. Os botões `cta`/`secondaryCta` deixam de ter cor forçada (branco sobre gradiente) e usam as variantes naturais `default`/`outline`, que já contrastam corretamente sobre `bg-card`. Nenhum outro consumidor deste bloco existe fora da `LandingPage`.
74
+ - **`PillBadge` vira um pill de verdade** — `rounded-[var(--radius)]` (6px) dá lugar a `rounded-full`, alinhando o componente ao próprio nome. Único consumidor é o eyebrow do `Hero`.
75
+ - **Recolorida a seção "Metodologia" da `LandingPage`** — os 4 passos passam de azul/celeste/amarelo/coral para coral/celeste/azul/laranja (`chart-4`/`chart-2`/`chart-1`/`warning`), aproximando a sequência de cores da referência de mockup (vermelho/verde-azulado/roxo/laranja); como o tema `xertica` não tem roxo, `chart-1` (azul) substitui esse passo — é um desvio conhecido da referência.
76
+
77
+ ---
78
+
79
+ ## [2.9.3] — 2026-07-13
80
+
81
+ ### Changed
82
+
83
+ - **Padronização das variantes de botão na landing page** — o botão de "iniciar sessão" (`MarketingNavbar`, desktop e mobile) e o `secondaryCta` do `Hero` passam a usar a variante `secondary` (preto). O `primaryCta` do `Hero` ("Começar agora") e o `cta` do `CTASection` continuam `default`/primary, reservado para as ações principais de conversão. A variante `outline` continua reservada para os cards (`ServiceCardGrid`). Mudança feita direto nos blocos, sem novas props — nenhum outro consumidor desses componentes existe fora da `LandingPage`.
84
+
85
+ ---
86
+
87
+ ## [2.9.2] — 2026-07-13
88
+
89
+ ### Changed
90
+
91
+ - **Tema `xertica` deixa de ter gradientes** — `gradientStart`/`gradientEnd` (claro e escuro) passam a apontar todos para o mesmo Negro Xertica (`#1E1E1E`), então `--gradient-diagonal` resolve para um preenchimento sólido nesse tema. Afeta todo consumidor do token (banner do `CTASection` na `LandingPage`, `AuthPageShell`, etc.) sem exigir mudança em nenhum componente — os demais temas mantêm seus gradientes normalmente.
92
+
93
+ ---
94
+
95
+ ## [2.9.1] — 2026-07-13
96
+
97
+ ### Changed
98
+
99
+ - **`StatBand` ganha alinhamento numérico e rótulo em estilo legenda** — o valor numérico agora usa `tabular-nums` (mesma técnica já usada no numeral fantasma do `SectionHeading`), e o rótulo passa de texto simples (`text-sm`) para o padrão de legenda já usado em `ServiceCardGrid` (`text-xs font-semibold uppercase tracking-wide`). Ritmo de grade mais generoso (`gap-y-8`→`gap-y-10`, `pl-8`→`pl-10` nos divisores).
100
+ - **Espaçamento título→texto corrido padronizado em `SectionHeading`, `FeatureRow`, `CTASection` e `EvidenceTimeline`** — na referência de Swiss Design, títulos e o texto corrido que os segue precisam de um espaço deliberado entre si. O gap `mt-3`/`mt-4` (12–16px) usado nos blocos de marketing da `LandingPage` passa para `mt-6` (24px) nos três primeiros; `EvidenceTimeline`, por ser um relacionamento secundário mais denso dentro de uma lista, recebe um ajuste proporcional menor (`mt-1`→`mt-2`).
101
+
102
+ ---
103
+
104
+ ## [2.9.0] — 2026-07-13
105
+
106
+ ### Added
107
+
108
+ - **`xertica-ui/blocks` ganha um novo namespace `marketing`** — `Hero`, `FeatureRow`, `CTASection`, `ComplianceBadgeRow`, `EvidenceTimeline`, `LogoCloud`, `MarketingFooter`, `MarketingNavbar`, `PillBadge`, `ServiceCardGrid` e `StatBand`, compondo a nova `LandingPage` (`xertica-ui/pages`, também registrada em `/landing` no template do CLI). Todos os visuais são placeholders orientados a tokens (isotipos da marca, SVGs inline), sem dependência de imagens externas.
109
+ - **Nova marca `IsotypeDiagonal`** — quarta variação da família `isotype` (`xertica-ui/brand`), com corte diagonal fixo formando pontas triangulares nos encontros azul/verde e magenta/vermelho e na hipotenusa do amarelo, as três sobre a mesma diagonal do quadrado. `AuthPageShell` ganha a prop `leftPanel="isotype"` (painel de marca sobre `bg-sidebar`, alternativa ao par imagem+gradiente) e `gradientClassName` (override do gradiente por página); `LoginPage` e o `LoginContent` do template do CLI passam a usar `leftPanel="isotype"`, substituindo a foto de estoque (Unsplash) que usavam antes.
110
+ - **`Button` variant `outline` ganha um token de contorno dedicado** — `--outline-border` / utilitário `border-outline-stroke` — com o mesmo mecanismo opcional (`BrandColors.outlineBorderColor`) já usado por `inputBorderColor`. Os 9 temas pré-existentes ficam pixel-idênticos (fallback para `--border`); o tema `xertica` passa a usar o mesmo Negro Xertica do contorno dos inputs.
111
+
112
+ ### Changed
113
+
114
+ - **`Isotype` (grid plano) e `IsotypeMini` saem de rotação** nas páginas do próprio design system (Hero da `LandingPage`, cabeçalho da `HomeContent`) — continuam exportados e totalmente funcionais, apenas comentados no ponto de uso para fácil reativação futura. Só `IsotypeDiagonal` e `IsotypeFrames` seguem em uso ativo.
115
+
116
+ ### Fixed
117
+
118
+ - **`Card` `variant="white"` podia renderizar texto ilegível no dark mode** — `text-card-foreground` (tema-reativo) era aplicado incondicionalmente, então um card forçado a branco herdava a cor de texto quase-branca do tema no dark mode. Agora `variant="white"` sempre pareia com `text-neutral-900` fixo, independente do tema/modo.
119
+
120
+ ---
121
+
14
122
  ## [2.8.0] — 2026-07-09
15
123
 
16
124
  ### Added
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Enterprise-grade React design system** built on Tailwind CSS v4, Radix UI, and Lucide Icons — with a robust AI-first documentation layer for precise LLM-driven composition and autonomous agent interaction.
4
4
 
5
- [![npm version](https://img.shields.io/badge/npm-2.7.0-blue)](https://www.npmjs.com/package/xertica-ui)
5
+ [![npm version](https://img.shields.io/badge/npm-2.9.7-blue)](https://www.npmjs.com/package/xertica-ui)
6
6
  [![license](https://img.shields.io/badge/license-proprietary-red)](./LICENSE)
7
7
 
8
8
  ---
package/bin/cli.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  syncLocaleFiles,
18
18
  generateI18nFile,
19
19
  generateAppTsx,
20
+ generateLandingAppTsx,
20
21
  } from './language-config';
21
22
 
22
23
  // ─────────────────────────────────────────────────────────────────────────────
@@ -31,6 +32,8 @@ interface XerticaConfig {
31
32
  hasAssistant: boolean;
32
33
  disableDarkMode?: boolean;
33
34
  themeId?: string;
35
+ /** Missing on projects scaffolded before this field existed — treat as 'system'. */
36
+ projectType?: 'page' | 'system';
34
37
  }
35
38
 
36
39
  async function readXerticaConfig(targetDir: string): Promise<XerticaConfig | null> {
@@ -286,6 +289,165 @@ export function TemplatePage() {
286
289
  `;
287
290
  }
288
291
 
292
+ // ─────────────────────────────────────────────────────────────────────────────
293
+ // package.json copy helper — shared by the 'system' and 'page' init flows.
294
+ // ─────────────────────────────────────────────────────────────────────────────
295
+
296
+ async function copyPackageJson(templatesDir: string, targetDir: string): Promise<void> {
297
+ const pkgTemplatePath = path.join(templatesDir, 'package.json');
298
+ if (await fs.pathExists(pkgTemplatePath)) {
299
+ const pkgContent = await fs.readJson(pkgTemplatePath);
300
+ const projectName = path.basename(targetDir) || 'my-xertica-app';
301
+ pkgContent.name = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
302
+ await fs.writeJson(path.join(targetDir, 'package.json'), pkgContent, { spaces: 2 });
303
+ }
304
+ }
305
+
306
+ // ─────────────────────────────────────────────────────────────────────────────
307
+ // Minimal "web page" scaffold — just the marketing LandingPage, no auth shell.
308
+ // ─────────────────────────────────────────────────────────────────────────────
309
+
310
+ async function initPageProject(targetDir: string, templatesDir: string): Promise<void> {
311
+ const response = await prompts([
312
+ {
313
+ type: 'multiselect',
314
+ name: 'languages',
315
+ message: 'Which languages should the page support?',
316
+ instructions: false,
317
+ hint: 'Select at least 1. Single-language apps auto-hide the LanguageSelector.',
318
+ min: 1,
319
+ choices: SUPPORTED_LANGUAGES.map(l => ({
320
+ title: l.label,
321
+ value: l.code,
322
+ selected: true,
323
+ })),
324
+ },
325
+ {
326
+ type: 'select',
327
+ name: 'theme',
328
+ message: 'Select the default color theme for your project:',
329
+ choices: colorThemes.map(t => ({
330
+ title: t.name,
331
+ description: t.description,
332
+ value: t.id,
333
+ })),
334
+ initial: 0,
335
+ },
336
+ {
337
+ type: 'confirm',
338
+ name: 'install',
339
+ message: 'Install dependencies automatically?',
340
+ initial: true,
341
+ },
342
+ ]);
343
+
344
+ if (!response.languages || !response.theme) return;
345
+
346
+ const spinner = ora('Initializing project...').start();
347
+
348
+ try {
349
+ await fs.ensureDir(targetDir);
350
+
351
+ const selectedLanguages: string[] =
352
+ Array.isArray(response.languages) && response.languages.length > 0
353
+ ? response.languages
354
+ : DEFAULT_SELECTION;
355
+
356
+ // 1. Copy root config files (no CLAUDE.md/guidelines — those document the
357
+ // FSD auth-app architecture, which this scaffold doesn't have; no
358
+ // .env.example — its keys are only used by the assistant/maps pages).
359
+ const rootFilesToCopy = [
360
+ 'index.html',
361
+ 'vite.config.ts',
362
+ 'tsconfig.json',
363
+ 'tsconfig.node.json',
364
+ 'postcss.config.js',
365
+ 'vite-env.d.ts',
366
+ 'eslint.config.js',
367
+ '.gitignore',
368
+ '.prettierrc',
369
+ ];
370
+
371
+ for (const file of rootFilesToCopy) {
372
+ const srcPath = path.join(templatesDir, file);
373
+ if (await fs.pathExists(srcPath)) {
374
+ await fs.copy(srcPath, path.join(targetDir, file));
375
+ }
376
+ }
377
+
378
+ // 2. Copy package.json
379
+ await copyPackageJson(templatesDir, targetDir);
380
+
381
+ // 3. Copy src/main.tsx (unchanged regardless of App.tsx variant)
382
+ await fs.copy(
383
+ path.join(templatesDir, 'src', 'main.tsx'),
384
+ path.join(targetDir, 'src', 'main.tsx')
385
+ );
386
+
387
+ // 4. Generate src/app/App.tsx rendering LandingPage directly
388
+ await fs.ensureDir(path.join(targetDir, 'src', 'app'));
389
+ await fs.writeFile(
390
+ path.join(targetDir, 'src', 'app', 'App.tsx'),
391
+ generateLandingAppTsx(selectedLanguages)
392
+ );
393
+
394
+ // 5. Generate i18n.ts + copy selected locales
395
+ await fs.writeFile(
396
+ path.join(targetDir, 'src', 'i18n.ts'),
397
+ generateI18nFile(selectedLanguages)
398
+ );
399
+ await syncLocaleFiles(templatesDir, targetDir, selectedLanguages, { pruneOthers: true });
400
+ await writeLanguagesConfig(targetDir, selectedLanguages);
401
+
402
+ // 6. Persist project config
403
+ await writeXerticaConfig(targetDir, {
404
+ hasAssistant: false,
405
+ disableDarkMode: false,
406
+ themeId: response.theme,
407
+ projectType: 'page',
408
+ });
409
+
410
+ // 7. Copy the LandingPage re-export shim
411
+ await fs.ensureDir(path.join(targetDir, 'src', 'pages'));
412
+ await fs.copy(
413
+ path.join(templatesDir, 'src', 'pages', 'LandingPage.tsx'),
414
+ path.join(targetDir, 'src', 'pages', 'LandingPage.tsx')
415
+ );
416
+
417
+ // 8. Generate theme tokens
418
+ const selectedTheme = colorThemes.find(t => t.id === response.theme) || colorThemes[0];
419
+ const tokensDir = path.join(targetDir, 'src', 'styles', 'xertica');
420
+ await fs.ensureDir(tokensDir);
421
+ await fs.copy(
422
+ path.join(templatesDir, 'src', 'styles', 'index.css'),
423
+ path.join(targetDir, 'src', 'styles', 'index.css')
424
+ );
425
+ await fs.writeFile(path.join(tokensDir, 'tokens.css'), generateTokensCss(selectedTheme));
426
+
427
+ spinner.succeed('Project initialized successfully!');
428
+
429
+ if (response.install) {
430
+ const installSpinner = ora('Installing dependencies...').start();
431
+ await execa('npm', ['install'], { cwd: targetDir });
432
+ installSpinner.succeed('Dependencies installed!');
433
+ }
434
+
435
+ console.log(chalk.green('\n✅ Done! Your Xertica UI landing page is ready.'));
436
+ console.log(chalk.cyan(`\n cd ${path.relative(process.cwd(), targetDir) || '.'}`));
437
+ if (!response.install) {
438
+ console.log(chalk.cyan(' npm install'));
439
+ }
440
+ console.log(chalk.cyan(' npm run dev'));
441
+ console.log();
442
+ console.log(chalk.gray(' Edit the demo content in src/pages/LandingPage.tsx'));
443
+ console.log(chalk.gray(' Customize the theme in src/styles/xertica/tokens.css'));
444
+ console.log(chalk.gray(' To change the theme or languages later: npx xertica-ui update'));
445
+ } catch (error) {
446
+ spinner.fail('Failed to initialize project');
447
+ console.error(error);
448
+ }
449
+ }
450
+
289
451
  const program = new Command();
290
452
 
291
453
  program
@@ -303,6 +465,32 @@ program
303
465
 
304
466
  console.log(chalk.blue(`🚀 Welcome to Xertica UI CLI! ${chalk.dim(`v${pkgJson.version}`)}`));
305
467
 
468
+ const { projectType } = await prompts({
469
+ type: 'select',
470
+ name: 'projectType',
471
+ message: 'What do you want to create?',
472
+ choices: [
473
+ {
474
+ title: 'Full system',
475
+ description: 'Auth, sidebar, pages, i18n — the current full scaffold',
476
+ value: 'system',
477
+ },
478
+ {
479
+ title: 'Web page',
480
+ description: 'Just the marketing landing page — pick a theme and languages, done',
481
+ value: 'page',
482
+ },
483
+ ],
484
+ initial: 0,
485
+ });
486
+
487
+ if (!projectType) return;
488
+
489
+ if (projectType === 'page') {
490
+ await initPageProject(targetDir, templatesDir);
491
+ return;
492
+ }
493
+
306
494
  const response = await prompts([
307
495
  {
308
496
  type: 'multiselect',
@@ -408,13 +596,7 @@ program
408
596
  }
409
597
 
410
598
  // 2. Copy package.json
411
- const pkgTemplatePath = path.join(templatesDir, 'package.json');
412
- if (await fs.pathExists(pkgTemplatePath)) {
413
- const pkgContent = await fs.readJson(pkgTemplatePath);
414
- const projectName = path.basename(targetDir) || 'my-xertica-app';
415
- pkgContent.name = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
416
- await fs.writeJson(path.join(targetDir, 'package.json'), pkgContent, { spaces: 2 });
417
- }
599
+ await copyPackageJson(templatesDir, targetDir);
418
600
 
419
601
  // 3. Copy src/main.tsx
420
602
  await fs.copy(
@@ -458,7 +640,12 @@ program
458
640
  await writeLanguagesConfig(targetDir, selectedLanguages);
459
641
 
460
642
  // 6.5 Persist project feature flags (.xertica.json)
461
- await writeXerticaConfig(targetDir, { hasAssistant, disableDarkMode, themeId: response.theme });
643
+ await writeXerticaConfig(targetDir, {
644
+ hasAssistant,
645
+ disableDarkMode,
646
+ themeId: response.theme,
647
+ projectType: 'system',
648
+ });
462
649
 
463
650
  // 6.4 Copy context
464
651
  await fs.ensureDir(path.join(targetDir, 'src', 'app', 'context'));
@@ -610,6 +797,8 @@ program
610
797
  console.log(chalk.blue(`🔧 Xertica UI CLI ${chalk.dim(`v${pkgJson.version}`)}`));
611
798
 
612
799
  const currentConfig = await readXerticaConfig(targetDir);
800
+ const projectType = currentConfig?.projectType ?? 'system';
801
+ const isPageProject = projectType === 'page';
613
802
 
614
803
  const { updateType } = await prompts({
615
804
  type: 'select',
@@ -626,13 +815,17 @@ program
626
815
  description: 'Add or remove supported languages (pt-BR, en, es, …)',
627
816
  value: 'languages',
628
817
  },
629
- {
630
- title: 'Assistant',
631
- description: currentConfig?.hasAssistant
632
- ? 'Remove the AI Assistant from your project'
633
- : 'Add the AI Assistant to your project',
634
- value: 'assistant',
635
- },
818
+ ...(isPageProject
819
+ ? []
820
+ : [
821
+ {
822
+ title: 'Assistant',
823
+ description: currentConfig?.hasAssistant
824
+ ? 'Remove the AI Assistant from your project'
825
+ : 'Add the AI Assistant to your project',
826
+ value: 'assistant',
827
+ },
828
+ ]),
636
829
  {
637
830
  title: 'Dark Mode',
638
831
  description: currentConfig?.disableDarkMode
@@ -642,7 +835,9 @@ program
642
835
  },
643
836
  {
644
837
  title: 'Project files',
645
- description: 'Update app shell, shared, features and pages to a specific version',
838
+ description: isPageProject
839
+ ? 'Update the app shell and landing page to a specific version'
840
+ : 'Update app shell, shared, features and pages to a specific version',
646
841
  value: 'project',
647
842
  },
648
843
  ],
@@ -798,7 +993,9 @@ program
798
993
  // 3) Regenerate App.tsx so the `availableLanguages` prop matches
799
994
  await fs.writeFile(
800
995
  path.join(targetDir, 'src', 'app', 'App.tsx'),
801
- generateAppTsx(newCodes, currentConfig?.disableDarkMode ?? false)
996
+ isPageProject
997
+ ? generateLandingAppTsx(newCodes, currentConfig?.disableDarkMode ?? false)
998
+ : generateAppTsx(newCodes, currentConfig?.disableDarkMode ?? false)
802
999
  );
803
1000
 
804
1001
  // 4) Persist the new selection
@@ -1014,7 +1211,9 @@ program
1014
1211
 
1015
1212
  await fs.writeFile(
1016
1213
  path.join(targetDir, 'src', 'app', 'App.tsx'),
1017
- generateAppTsx(selectedCodes, newDisableDarkMode)
1214
+ isPageProject
1215
+ ? generateLandingAppTsx(selectedCodes, newDisableDarkMode)
1216
+ : generateAppTsx(selectedCodes, newDisableDarkMode)
1018
1217
  );
1019
1218
 
1020
1219
  spinner.succeed(
@@ -1063,38 +1262,53 @@ program
1063
1262
  type: 'multiselect',
1064
1263
  name: 'filesToUpdate',
1065
1264
  message: 'Select which parts of the project to update:',
1066
- choices: [
1067
- {
1068
- title: 'App shell (src/app/)',
1069
- description: 'App.tsx, AppLayout.tsx',
1070
- value: 'app',
1071
- selected: true,
1072
- },
1073
- {
1074
- title: 'Shared utilities (src/shared/)',
1075
- description: 'auth.ts, navigation.ts, types',
1076
- value: 'shared',
1077
- selected: true,
1078
- },
1079
- {
1080
- title: 'Features (src/features/)',
1081
- description: 'auth, home, template UI components',
1082
- value: 'features',
1083
- selected: true,
1084
- },
1085
- {
1086
- title: 'Pages (src/pages/)',
1087
- description: 'Thin page wrapper components',
1088
- value: 'pages',
1089
- selected: true,
1090
- },
1091
- {
1092
- title: 'Root config files',
1093
- description: 'vite.config.ts, tsconfig.json, etc.',
1094
- value: 'config',
1095
- selected: false,
1096
- },
1097
- ],
1265
+ choices: isPageProject
1266
+ ? [
1267
+ {
1268
+ title: 'App shell + landing page',
1269
+ description: 'App.tsx and the LandingPage re-export',
1270
+ value: 'app',
1271
+ selected: true,
1272
+ },
1273
+ {
1274
+ title: 'Root config files',
1275
+ description: 'vite.config.ts, tsconfig.json, etc.',
1276
+ value: 'config',
1277
+ selected: false,
1278
+ },
1279
+ ]
1280
+ : [
1281
+ {
1282
+ title: 'App shell (src/app/)',
1283
+ description: 'App.tsx, AppLayout.tsx',
1284
+ value: 'app',
1285
+ selected: true,
1286
+ },
1287
+ {
1288
+ title: 'Shared utilities (src/shared/)',
1289
+ description: 'auth.ts, navigation.ts, types',
1290
+ value: 'shared',
1291
+ selected: true,
1292
+ },
1293
+ {
1294
+ title: 'Features (src/features/)',
1295
+ description: 'auth, home, template UI components',
1296
+ value: 'features',
1297
+ selected: true,
1298
+ },
1299
+ {
1300
+ title: 'Pages (src/pages/)',
1301
+ description: 'Thin page wrapper components',
1302
+ value: 'pages',
1303
+ selected: true,
1304
+ },
1305
+ {
1306
+ title: 'Root config files',
1307
+ description: 'vite.config.ts, tsconfig.json, etc.',
1308
+ value: 'config',
1309
+ selected: false,
1310
+ },
1311
+ ],
1098
1312
  });
1099
1313
 
1100
1314
  if (!filesToUpdate || filesToUpdate.length === 0) return;
@@ -1124,13 +1338,6 @@ program
1124
1338
  const updatedTemplatesDir = path.join(targetDir, 'node_modules', 'xertica-ui', 'templates');
1125
1339
 
1126
1340
  if (filesToUpdate.includes('app')) {
1127
- // AppLayout is always safe to overwrite (no per-project config baked in)
1128
- await fs.copy(
1129
- path.join(updatedTemplatesDir, 'src', 'app', 'components', 'AppLayout.tsx'),
1130
- path.join(targetDir, 'src', 'app', 'components', 'AppLayout.tsx'),
1131
- { overwrite: true }
1132
- );
1133
-
1134
1341
  // For App.tsx and i18n.ts we must preserve the user's language selection.
1135
1342
  // Read it from .languages.json (or fall back to inspecting locales/ for
1136
1343
  // projects scaffolded before the file existed).
@@ -1152,38 +1359,71 @@ program
1152
1359
 
1153
1360
  const projectConfig = await readXerticaConfig(targetDir);
1154
1361
 
1155
- // Regenerate App.tsx and i18n.ts honoring the persisted language set
1156
- await fs.writeFile(
1157
- path.join(targetDir, 'src', 'app', 'App.tsx'),
1158
- generateAppTsx(selectedCodes, projectConfig?.disableDarkMode ?? false)
1159
- );
1160
- await fs.writeFile(path.join(targetDir, 'src', 'i18n.ts'), generateI18nFile(selectedCodes));
1362
+ if (isPageProject) {
1363
+ // Regenerate App.tsx and i18n.ts, re-copy the LandingPage shim —
1364
+ // no AppLayout/AuthGuard exist in a page-only project.
1365
+ await fs.writeFile(
1366
+ path.join(targetDir, 'src', 'app', 'App.tsx'),
1367
+ generateLandingAppTsx(selectedCodes, projectConfig?.disableDarkMode ?? false)
1368
+ );
1369
+ await fs.writeFile(
1370
+ path.join(targetDir, 'src', 'i18n.ts'),
1371
+ generateI18nFile(selectedCodes)
1372
+ );
1373
+ await syncLocaleFiles(updatedTemplatesDir, targetDir, selectedCodes, {
1374
+ pruneOthers: true,
1375
+ });
1376
+ await fs.copy(
1377
+ path.join(updatedTemplatesDir, 'src', 'pages', 'LandingPage.tsx'),
1378
+ path.join(targetDir, 'src', 'pages', 'LandingPage.tsx'),
1379
+ { overwrite: true }
1380
+ );
1381
+ } else {
1382
+ // AppLayout is always safe to overwrite (no per-project config baked in)
1383
+ await fs.copy(
1384
+ path.join(updatedTemplatesDir, 'src', 'app', 'components', 'AppLayout.tsx'),
1385
+ path.join(targetDir, 'src', 'app', 'components', 'AppLayout.tsx'),
1386
+ { overwrite: true }
1387
+ );
1161
1388
 
1162
- // Refresh locale JSON files for the selected languages (keys grow over
1163
- // library updates) — but prune any orphans from prior selections.
1164
- await syncLocaleFiles(updatedTemplatesDir, targetDir, selectedCodes, {
1165
- pruneOthers: true,
1166
- });
1389
+ // Regenerate App.tsx and i18n.ts honoring the persisted language set
1390
+ await fs.writeFile(
1391
+ path.join(targetDir, 'src', 'app', 'App.tsx'),
1392
+ generateAppTsx(selectedCodes, projectConfig?.disableDarkMode ?? false)
1393
+ );
1394
+ await fs.writeFile(
1395
+ path.join(targetDir, 'src', 'i18n.ts'),
1396
+ generateI18nFile(selectedCodes)
1397
+ );
1167
1398
 
1168
- // Regenerate AuthGuard preserving the current page set and assistant flag
1169
- const pagesDir = path.join(targetDir, 'src', 'pages');
1170
- const existingPages = (await fs.pathExists(pagesDir)) ? await fs.readdir(pagesDir) : [];
1171
- const hasLoginP = existingPages.includes('LoginPage.tsx');
1172
- const hasHomeP = existingPages.includes('HomePage.tsx');
1173
- const hasTemplateP = existingPages.includes('TemplatePage.tsx');
1174
- const hasAssistantP =
1175
- projectConfig?.hasAssistant ?? existingPages.includes('AssistantPage.tsx');
1176
- const firstProtectedP = hasHomeP ? '/home' : hasTemplateP ? '/template' : '/login';
1177
- await fs.writeFile(
1178
- path.join(targetDir, 'src', 'app', 'components', 'AuthGuard.tsx'),
1179
- generateAuthGuard({
1180
- hasLogin: hasLoginP,
1181
- hasHome: hasHomeP,
1182
- hasTemplate: hasTemplateP,
1183
- hasAssistant: hasAssistantP,
1184
- firstProtectedPath: firstProtectedP,
1185
- })
1186
- );
1399
+ // Refresh locale JSON files for the selected languages (keys grow over
1400
+ // library updates) but prune any orphans from prior selections.
1401
+ await syncLocaleFiles(updatedTemplatesDir, targetDir, selectedCodes, {
1402
+ pruneOthers: true,
1403
+ });
1404
+
1405
+ // Regenerate AuthGuard preserving the current page set and assistant flag
1406
+ const pagesDir = path.join(targetDir, 'src', 'pages');
1407
+ const existingPages = (await fs.pathExists(pagesDir))
1408
+ ? await fs.readdir(pagesDir)
1409
+ : [];
1410
+ const hasLoginP = existingPages.includes('LoginPage.tsx');
1411
+ const hasHomeP = existingPages.includes('HomePage.tsx');
1412
+ const hasTemplateP = existingPages.includes('TemplatePage.tsx');
1413
+ const hasAssistantP =
1414
+ projectConfig?.hasAssistant ?? existingPages.includes('AssistantPage.tsx');
1415
+ const firstProtectedP = hasHomeP ? '/home' : hasTemplateP ? '/template' : '/login';
1416
+ await fs.writeFile(
1417
+ path.join(targetDir, 'src', 'app', 'components', 'AuthGuard.tsx'),
1418
+ generateAuthGuard({
1419
+ hasLogin: hasLoginP,
1420
+ hasHome: hasHomeP,
1421
+ hasTemplate: hasTemplateP,
1422
+ hasAssistant: hasAssistantP,
1423
+ firstProtectedPath: firstProtectedP,
1424
+ })
1425
+ );
1426
+ }
1187
1427
  }
1188
1428
 
1189
1429
  if (filesToUpdate.includes('shared')) {