xertica-ui 2.8.0 → 2.9.6

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 (285) hide show
  1. package/CHANGELOG.md +98 -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/contexts/BrandColorsContext.tsx +9 -5
  59. package/contexts/theme-data.ts +32 -5
  60. package/dist/AssistantChart-BU48lxL2.js +3383 -0
  61. package/dist/AssistantChart-CoN6ga6Z.js +3383 -0
  62. package/dist/AssistantChart-DIRYJ7WY.js +3383 -0
  63. package/dist/AssistantChart-DOnY1ZlD.cjs +3551 -0
  64. package/dist/AssistantChart-DjM3qnp5.cjs +3551 -0
  65. package/dist/AssistantChart-hxPyE1S9.cjs +3551 -0
  66. package/dist/AudioPlayer-CpihsbaD.js +1278 -0
  67. package/dist/AudioPlayer-CvGajzMC.cjs +1282 -0
  68. package/dist/AudioPlayer-CvHpFqDG.cjs +1282 -0
  69. package/dist/AudioPlayer-D4S7GQou.js +1278 -0
  70. package/dist/BrandColorsContext-BNKLUVcU.js +829 -0
  71. package/dist/BrandColorsContext-DNZGHItQ.cjs +836 -0
  72. package/dist/BrandColorsContext-Ug_TZ8yn.cjs +846 -0
  73. package/dist/BrandColorsContext-z0O2LlZz.js +839 -0
  74. package/dist/CodeBlock-BkaZf-R8.cjs +223 -0
  75. package/dist/CodeBlock-CKu8LgbP.cjs +223 -0
  76. package/dist/CodeBlock-DiLS5LLI.js +221 -0
  77. package/dist/CodeBlock-zdylBGdZ.js +221 -0
  78. package/dist/CustomTooltipContent-BAR5qCQg.js +33 -0
  79. package/dist/CustomTooltipContent-CBLHBxX8.cjs +54 -0
  80. package/dist/CustomTooltipContent-CjWm3jjV.js +33 -0
  81. package/dist/CustomTooltipContent-DTV9MN9E.cjs +54 -0
  82. package/dist/EvidenceTimeline-DrRk4yqb.cjs +560 -0
  83. package/dist/EvidenceTimeline-l4MaV4C7.js +546 -0
  84. package/dist/FeatureCard-CIbBHqeY.cjs +497 -0
  85. package/dist/FeatureCard-DtoKPSfW.cjs +497 -0
  86. package/dist/FeatureCard-P4iEpBmB.js +485 -0
  87. package/dist/FeatureCard-e3f8bnF0.js +485 -0
  88. package/dist/ImageWithFallback-C1ww9Hg3.cjs +122 -0
  89. package/dist/ImageWithFallback-CBrkAEo3.cjs +122 -0
  90. package/dist/ImageWithFallback-CGk5zPH-.js +98 -0
  91. package/dist/ImageWithFallback-CfnoFFSP.js +98 -0
  92. package/dist/Isotype--fRZWwS6.js +85 -0
  93. package/dist/Isotype-0dpLqetB.js +85 -0
  94. package/dist/Isotype-B8XLEXSB.cjs +92 -0
  95. package/dist/Isotype-ChKwN7e-.cjs +91 -0
  96. package/dist/IsotypeDiagonal-CuPYPP4f.js +84 -0
  97. package/dist/IsotypeDiagonal-eNcOXRAc.cjs +91 -0
  98. package/dist/IsotypeDiagonal-oiwpXZhE.js +104 -0
  99. package/dist/IsotypeDiagonal-xMFoFXkb.cjs +109 -0
  100. package/dist/IsotypeFrames-BcWtyAWp.cjs +83 -0
  101. package/dist/IsotypeFrames-BnvSJKiH.cjs +73 -0
  102. package/dist/IsotypeFrames-iDRTRuIu.js +79 -0
  103. package/dist/IsotypeFrames-qoXOvOJt.js +69 -0
  104. package/dist/IsotypeMini-CQtWVo3h.js +126 -0
  105. package/dist/IsotypeMini-Cu_sW5yN.cjs +134 -0
  106. package/dist/IsotypeTwist-BTdNL4iV.js +106 -0
  107. package/dist/IsotypeTwist-DvsUgZs_.cjs +115 -0
  108. package/dist/LanguageContext-B1aOXO-K.js +1987 -0
  109. package/dist/LanguageContext-BP0k7AzI.js +1993 -0
  110. package/dist/LanguageContext-CChcIHIG.cjs +1998 -0
  111. package/dist/LanguageContext-ubC7TSs_.cjs +1992 -0
  112. package/dist/LanguageSelector-BLFPXGo8.cjs +48 -0
  113. package/dist/LanguageSelector-CYRzWkRZ.cjs +48 -0
  114. package/dist/LanguageSelector-CyHjZgC4.cjs +48 -0
  115. package/dist/LanguageSelector-DE8lIySO.js +46 -0
  116. package/dist/LanguageSelector-DUr1hjBW.js +46 -0
  117. package/dist/LanguageSelector-DjbWIKWC.js +46 -0
  118. package/dist/LogoCloud-C_uMKVNg.cjs +513 -0
  119. package/dist/LogoCloud-DJsMPNPh.js +500 -0
  120. package/dist/MarketingFooter-B_ycEdTJ.cjs +727 -0
  121. package/dist/MarketingFooter-Bhx-2Vlr.cjs +727 -0
  122. package/dist/MarketingFooter-Br1MGK9e.js +699 -0
  123. package/dist/MarketingFooter-BwE1vmeX.cjs +700 -0
  124. package/dist/MarketingFooter-CsZ1_xL9.js +709 -0
  125. package/dist/MarketingFooter-DCwf78Q4.cjs +716 -0
  126. package/dist/MarketingFooter-KH2r-OOL.js +709 -0
  127. package/dist/MarketingFooter-nB7G7gQ5.js +682 -0
  128. package/dist/SectionHeading-DEeOKcAd.cjs +857 -0
  129. package/dist/SectionHeading-DMNObcwm.js +838 -0
  130. package/dist/ThemeToggle--CGJjQJv.cjs +219 -0
  131. package/dist/ThemeToggle-Bn_7Pmfe.js +215 -0
  132. package/dist/ThemeToggle-CoL8Ady7.js +215 -0
  133. package/dist/ThemeToggle-D2Wzm5bn.cjs +219 -0
  134. package/dist/VerifyEmailPage-90lK6pP4.js +3258 -0
  135. package/dist/VerifyEmailPage-BJX1Rter.cjs +3320 -0
  136. package/dist/VerifyEmailPage-BSWvOgry.js +3310 -0
  137. package/dist/VerifyEmailPage-CDqIkhx-.js +3224 -0
  138. package/dist/VerifyEmailPage-Crxje3ZB.js +3323 -0
  139. package/dist/VerifyEmailPage-D2ShbuEN.cjs +3333 -0
  140. package/dist/VerifyEmailPage-DEBZXQ7c.cjs +3310 -0
  141. package/dist/VerifyEmailPage-DGaX-jZv.cjs +3268 -0
  142. package/dist/VerifyEmailPage-ZtOPkgOA.cjs +3234 -0
  143. package/dist/VerifyEmailPage-s927GSiR.js +3300 -0
  144. package/dist/XerticaOrbe-BHex09xs.js +1925 -0
  145. package/dist/XerticaOrbe-BKW3LJEz.js +1925 -0
  146. package/dist/XerticaOrbe-Br2i99U_.cjs +1927 -0
  147. package/dist/XerticaOrbe-kjFmuyx-.cjs +1927 -0
  148. package/dist/XerticaProvider-Bi5lKC9N.js +44 -0
  149. package/dist/XerticaProvider-BrPMVhiv.js +44 -0
  150. package/dist/XerticaProvider-CnX5kt5W.cjs +46 -0
  151. package/dist/XerticaProvider-DEKNJTlN.js +44 -0
  152. package/dist/XerticaProvider-X_wavDYA.cjs +46 -0
  153. package/dist/XerticaProvider-b3n_oL8w.cjs +46 -0
  154. package/dist/alert-dialog--qdlmB5i.cjs +872 -0
  155. package/dist/alert-dialog-C48TOdYU.cjs +872 -0
  156. package/dist/alert-dialog-CEPpnpXt.js +819 -0
  157. package/dist/alert-dialog-NjflitDa.js +819 -0
  158. package/dist/assistant.cjs.js +2 -2
  159. package/dist/assistant.es.js +2 -2
  160. package/dist/avatar-CtEKgg7s.cjs +77 -0
  161. package/dist/avatar-D03MNQxs.js +54 -0
  162. package/dist/avatar-Df7KgSvs.js +54 -0
  163. package/dist/avatar-Gy0_Xu9i.cjs +77 -0
  164. package/dist/blocks.cjs.js +33 -18
  165. package/dist/blocks.es.js +2 -2
  166. package/dist/brand.cjs.js +110 -4
  167. package/dist/brand.es.js +98 -4
  168. package/dist/breadcrumb-BGO5PZPG.js +90 -0
  169. package/dist/breadcrumb-BvGAY4ne.cjs +98 -0
  170. package/dist/breadcrumb-CU-punra.js +90 -0
  171. package/dist/breadcrumb-DjV9wfX1.cjs +98 -0
  172. package/dist/button-B05czY_8.js +62 -0
  173. package/dist/button-B3SE1liJ.js +62 -0
  174. package/dist/button-BMDlWZR8.cjs +85 -0
  175. package/dist/button-DVhloDtr.cjs +85 -0
  176. package/dist/cli.js +280 -62
  177. package/dist/components/blocks/auth/AuthPageShell.d.ts +21 -5
  178. package/dist/components/blocks/index.d.ts +1 -0
  179. package/dist/components/blocks/marketing/CTASection.d.ts +28 -0
  180. package/dist/components/blocks/marketing/ComplianceBadgeRow.d.ts +23 -0
  181. package/dist/components/blocks/marketing/EvidenceTimeline.d.ts +26 -0
  182. package/dist/components/blocks/marketing/FeatureRow.d.ts +25 -0
  183. package/dist/components/blocks/marketing/Hero.d.ts +35 -0
  184. package/dist/components/blocks/marketing/HowItWorksSteps.d.ts +29 -0
  185. package/dist/components/blocks/marketing/LogoCloud.d.ts +22 -0
  186. package/dist/components/blocks/marketing/MarketingFooter.d.ts +42 -0
  187. package/dist/components/blocks/marketing/MarketingNavbar.d.ts +47 -0
  188. package/dist/components/blocks/marketing/PillBadge.d.ts +21 -0
  189. package/dist/components/blocks/marketing/SectionHeading.d.ts +23 -0
  190. package/dist/components/blocks/marketing/ServiceCardGrid.d.ts +65 -0
  191. package/dist/components/blocks/marketing/StatBand.d.ts +25 -0
  192. package/dist/components/blocks/marketing/TopAccentCards.d.ts +29 -0
  193. package/dist/components/blocks/marketing/index.d.ts +28 -0
  194. package/dist/components/brand/index.d.ts +1 -0
  195. package/dist/components/brand/isotype/Isotype.d.ts +27 -0
  196. package/dist/components/brand/isotype/IsotypeDiagonal.d.ts +19 -0
  197. package/dist/components/brand/isotype/IsotypeFrames.d.ts +21 -0
  198. package/dist/components/brand/isotype/IsotypeMini.d.ts +26 -0
  199. package/dist/components/brand/isotype/IsotypeTwist.d.ts +22 -0
  200. package/dist/components/brand/isotype/frame-variants.d.ts +11 -0
  201. package/dist/components/brand/isotype/index.d.ts +9 -0
  202. package/dist/components/brand/isotype/mini-patterns.d.ts +25 -0
  203. package/dist/components/brand/isotype/patterns.d.ts +42 -0
  204. package/dist/components/brand/isotype/twist-variants.d.ts +11 -0
  205. package/dist/components/pages/forgot-password-page/ForgotPasswordPage.d.ts +2 -2
  206. package/dist/components/pages/index.d.ts +1 -0
  207. package/dist/components/pages/landing-page/LandingPage.d.ts +18 -0
  208. package/dist/components/pages/landing-page/index.d.ts +1 -0
  209. package/dist/components/pages/login-page/LoginPage.d.ts +2 -2
  210. package/dist/components/pages/verify-email-page/VerifyEmailPage.d.ts +1 -1
  211. package/dist/components/ui/card/card.d.ts +3 -1
  212. package/dist/contexts/theme-data.d.ts +6 -0
  213. package/dist/dropdown-menu-ChQa9zW3.cjs +225 -0
  214. package/dist/dropdown-menu-DRvnsQAt.cjs +225 -0
  215. package/dist/dropdown-menu-DUk81i5Y.js +190 -0
  216. package/dist/dropdown-menu-NlUHhVmK.js +190 -0
  217. package/dist/hooks.cjs.js +2 -2
  218. package/dist/hooks.es.js +2 -2
  219. package/dist/index.cjs.js +57 -41
  220. package/dist/index.es.js +25 -25
  221. package/dist/input-Csp_Metb.cjs +91 -0
  222. package/dist/input-DVRwGVLx.js +65 -0
  223. package/dist/input-Dqyj7Hdw.cjs +91 -0
  224. package/dist/input-dOgrFQ-3.js +65 -0
  225. package/dist/layout.cjs.js +1 -1
  226. package/dist/layout.es.js +1 -1
  227. package/dist/media.cjs.js +1 -1
  228. package/dist/media.es.js +1 -1
  229. package/dist/pages.cjs.js +2 -1
  230. package/dist/pages.es.js +1 -1
  231. package/dist/rich-text-editor-7K7UjLJN.cjs +2968 -0
  232. package/dist/rich-text-editor-B3jLP2bm.cjs +2968 -0
  233. package/dist/rich-text-editor-BntMVQsB.js +2894 -0
  234. package/dist/rich-text-editor-Diz_onyB.js +2894 -0
  235. package/dist/rich-text-editor-RWvK4hd3.cjs +2968 -0
  236. package/dist/rich-text-editor-gauEkzPM.js +2894 -0
  237. package/dist/select-B6PygS38.cjs +161 -0
  238. package/dist/select-BD763J5Y.cjs +161 -0
  239. package/dist/select-QJDKZITN.js +130 -0
  240. package/dist/select-q7aDfuPn.js +130 -0
  241. package/dist/sidebar-Bkk1YAsD.cjs +1154 -0
  242. package/dist/sidebar-CuzYeOSz.cjs +1154 -0
  243. package/dist/sidebar-CwlW3LXE.js +1151 -0
  244. package/dist/sidebar-DNkf-Ug6.js +1151 -0
  245. package/dist/sidebar-DaS3fplw.cjs +1154 -0
  246. package/dist/sidebar-FDCY42oP.js +1151 -0
  247. package/dist/skeleton-BtBwJ39W.js +79 -0
  248. package/dist/skeleton-CJyg0eJ1.cjs +88 -0
  249. package/dist/skeleton-D8WaauK0.cjs +88 -0
  250. package/dist/skeleton-DBYtjaZW.cjs +88 -0
  251. package/dist/skeleton-DY_lSFWf.js +79 -0
  252. package/dist/skeleton-Df65Ox10.js +79 -0
  253. package/dist/slider-BKV_vvsh.cjs +78 -0
  254. package/dist/slider-B_lUjFNa.cjs +78 -0
  255. package/dist/slider-CtBpIaU4.js +56 -0
  256. package/dist/slider-KgWmSJbx.js +56 -0
  257. package/dist/tooltip-BPttuPxU.js +48 -0
  258. package/dist/tooltip-CIKRzQqE.cjs +72 -0
  259. package/dist/tooltip-CoDByjE_.cjs +72 -0
  260. package/dist/tooltip-_MAwsiOL.js +48 -0
  261. package/dist/ui.cjs.js +13 -13
  262. package/dist/ui.es.js +13 -13
  263. package/dist/xertica-assistant-B1xAl2Vn.js +2173 -0
  264. package/dist/xertica-assistant-BBUMaUXw.js +2173 -0
  265. package/dist/xertica-assistant-CWuBwfzt.cjs +2180 -0
  266. package/dist/xertica-assistant-D2hlwiEP.js +2173 -0
  267. package/dist/xertica-assistant-DN2J_sQQ.cjs +2180 -0
  268. package/dist/xertica-assistant-DdYS6WXT.cjs +2180 -0
  269. package/dist/xertica-ui.css +1 -1
  270. package/llms-compact.txt +1 -1
  271. package/llms.txt +1 -1
  272. package/package.json +1 -1
  273. package/styles/xertica/base.css +8 -0
  274. package/styles/xertica/tokens.css +1 -0
  275. package/templates/package.json +1 -1
  276. package/templates/src/app/components/AuthGuard.tsx +6 -0
  277. package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +1 -4
  278. package/templates/src/features/auth/ui/LoginContent.tsx +1 -4
  279. package/templates/src/features/auth/ui/ResetPasswordContent.tsx +1 -4
  280. package/templates/src/features/auth/ui/VerifyEmailContent.tsx +1 -4
  281. package/templates/src/locales/en/pages/landing.json +175 -0
  282. package/templates/src/locales/es/pages/landing.json +175 -0
  283. package/templates/src/locales/pt-BR/pages/landing.json +175 -0
  284. package/templates/src/pages/LandingPage.tsx +3 -0
  285. package/templates/src/styles/xertica/tokens.css +1 -0
@@ -299,24 +299,7 @@ export function generateAppTsx(selectedCodes: string[], disableDarkMode: boolean
299
299
  const selectedLangs = SUPPORTED_LANGUAGES.filter(l => selectedCodes.includes(l.code));
300
300
  const isMonolingual = selectedLangs.length === 1;
301
301
  const disableDarkModeProp = disableDarkMode ? `\n disableDarkMode={true}` : '';
302
- const isAllDefaults =
303
- selectedLangs.length === SUPPORTED_LANGUAGES.length &&
304
- selectedLangs.every(l => DEFAULT_SELECTION.includes(l.code));
305
-
306
- // Decide whether and how to render the `availableLanguages` prop on
307
- // XerticaProvider. When the user selected all 3 defaults, we can omit the
308
- // prop (the library default matches). Otherwise we emit the explicit set.
309
- const languagesArrayLiteral = selectedLangs
310
- .map(
311
- l => ` { code: '${l.code}', label: '${l.label}', shortLabel: '${l.shortLabel}' },`
312
- )
313
- .join('\n');
314
-
315
- const availableLanguagesProp = isAllDefaults
316
- ? ''
317
- : `\n availableLanguages={[
318
- ${languagesArrayLiteral}
319
- ]}`;
302
+ const availableLanguagesProp = buildAvailableLanguagesProp(selectedCodes, ' ');
320
303
 
321
304
  const monolingualBanner = isMonolingual
322
305
  ? `\n// This project is configured for a single language — the LanguageSelector
@@ -376,8 +359,65 @@ export default function App() {
376
359
  `;
377
360
  }
378
361
 
362
+ /**
363
+ * Generate the App.tsx source for a minimal "web page" project — renders the
364
+ * marketing `LandingPage` directly under `XerticaProvider`, with none of the
365
+ * router/query-client/auth machinery the full system scaffold needs (the
366
+ * landing page itself has no dependency on any of it).
367
+ */
368
+ export function generateLandingAppTsx(
369
+ selectedCodes: string[],
370
+ disableDarkMode: boolean = false
371
+ ): string {
372
+ const disableDarkModeProp = disableDarkMode ? `\n disableDarkMode={true}` : '';
373
+ const availableLanguagesProp = buildAvailableLanguagesProp(selectedCodes, ' ');
374
+
375
+ return `import React from 'react';
376
+ import { XerticaProvider } from 'xertica-ui/brand';
377
+ import { LandingPage } from 'xertica-ui/pages';
378
+
379
+ export default function App() {
380
+ return (
381
+ <XerticaProvider
382
+ useCustomTokens={true}${availableLanguagesProp}${disableDarkModeProp}
383
+ >
384
+ <LandingPage />
385
+ </XerticaProvider>
386
+ );
387
+ }
388
+ `;
389
+ }
390
+
379
391
  // ── Internal ─────────────────────────────────────────────────────────────────
380
392
 
393
+ /**
394
+ * Build the `availableLanguages` prop source for `<XerticaProvider>`. When the
395
+ * selection matches the library's default 3 languages, the prop is omitted
396
+ * entirely (the library default already matches). `indent` is the whitespace
397
+ * prefix the prop line itself should have — array items are indented one
398
+ * level deeper.
399
+ */
400
+ function buildAvailableLanguagesProp(selectedCodes: string[], indent: string): string {
401
+ const selectedLangs = SUPPORTED_LANGUAGES.filter(l => selectedCodes.includes(l.code));
402
+ const isAllDefaults =
403
+ selectedLangs.length === SUPPORTED_LANGUAGES.length &&
404
+ selectedLangs.every(l => DEFAULT_SELECTION.includes(l.code));
405
+
406
+ if (isAllDefaults) return '';
407
+
408
+ const itemIndent = `${indent} `;
409
+ const languagesArrayLiteral = selectedLangs
410
+ .map(
411
+ l =>
412
+ `${itemIndent}{ code: '${l.code}', label: '${l.label}', shortLabel: '${l.shortLabel}' },`
413
+ )
414
+ .join('\n');
415
+
416
+ return `\n${indent}availableLanguages={[
417
+ ${languagesArrayLiteral}
418
+ ${indent}]}`;
419
+ }
420
+
381
421
  /** Convert a BCP-47 code to a safe JS identifier (e.g. 'pt-BR' → 'ptBR'). */
382
422
  function toJsIdent(code: string): string {
383
423
  return code.replace(/[^a-zA-Z0-9]/g, '');
@@ -1,35 +1,80 @@
1
1
  import React from 'react';
2
2
  import { ImageWithFallback } from '../../figma/ImageWithFallback';
3
3
  import { LanguageSelector } from '../../brand/language-selector';
4
+ import { IsotypeTwist } from '../../brand/isotype';
5
+ import { cn } from '../../shared/utils';
4
6
 
5
- export interface AuthPageShellProps {
6
- imageSrc: string;
7
- imageAlt: string;
8
- children: React.ReactNode;
9
- }
7
+ export type AuthPageShellProps =
8
+ | {
9
+ /** Hero photo on the left (default). Requires `imageSrc`/`imageAlt`. */
10
+ leftPanel?: 'image';
11
+ imageSrc: string;
12
+ imageAlt: string;
13
+ /**
14
+ * Background classes for the hero image overlay gradient. Defaults to the
15
+ * shared `--gradient-diagonal` theme token; pass a Tailwind gradient to
16
+ * override it for a specific auth page without affecting the others.
17
+ */
18
+ gradientClassName?: string;
19
+ children: React.ReactNode;
20
+ }
21
+ | {
22
+ /**
23
+ * Brand isotype mark on a solid `bg-muted` panel — no photo, no
24
+ * gradient. Pairs with a lighter `bg-card` form panel on the right,
25
+ * the inverse of the `image` variant's `bg-muted` form panel.
26
+ */
27
+ leftPanel: 'isotype';
28
+ children: React.ReactNode;
29
+ };
10
30
 
11
31
  /**
12
32
  * Shared two-column shell used by every auth page (Login, ForgotPassword,
13
- * ResetPassword, VerifyEmail): hero image on the left, centered form panel
14
- * with a language selector on the right.
33
+ * ResetPassword, VerifyEmail): a left panel (hero image or brand isotype) and
34
+ * a centered form panel with a language selector on the right.
15
35
  */
16
- export function AuthPageShell({ imageSrc, imageAlt, children }: AuthPageShellProps) {
36
+ export function AuthPageShell(props: AuthPageShellProps) {
37
+ const { children } = props;
38
+
17
39
  return (
18
40
  <div className="h-screen w-full flex overflow-y-auto">
19
- <div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
20
- <ImageWithFallback
21
- src={imageSrc}
22
- alt={imageAlt}
23
- className="absolute inset-0 w-full h-full object-cover"
24
- />
25
- <div className="absolute inset-0 bg-[image:var(--gradient-diagonal)] opacity-80" />
26
- </div>
41
+ {props.leftPanel === 'isotype' ? (
42
+ <div className="hidden lg:flex lg:flex-1 items-center justify-center bg-muted">
43
+ <IsotypeTwist variant="magenta-blue" className="w-full max-w-sm" />
44
+ </div>
45
+ ) : (
46
+ <div className="hidden lg:flex lg:flex-1 relative overflow-hidden">
47
+ <ImageWithFallback
48
+ src={props.imageSrc}
49
+ alt={props.imageAlt}
50
+ className="absolute inset-0 w-full h-full object-cover"
51
+ />
52
+ <div
53
+ className={cn(
54
+ 'absolute inset-0 opacity-80',
55
+ props.gradientClassName ?? 'bg-[image:var(--gradient-diagonal)]'
56
+ )}
57
+ />
58
+ </div>
59
+ )}
27
60
 
28
- <div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative bg-muted">
61
+ <div
62
+ className={cn(
63
+ 'flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 lg:flex-none lg:w-1/2 relative',
64
+ props.leftPanel === 'isotype' ? 'bg-card' : 'bg-muted'
65
+ )}
66
+ >
29
67
  <div className="absolute top-4 right-4 z-20">
30
68
  <LanguageSelector variant="minimal" showIcon={false} />
31
69
  </div>
32
- <div className="absolute inset-0 lg:hidden bg-[image:var(--gradient-diagonal)] opacity-10 dark:opacity-5" />
70
+ {props.leftPanel !== 'isotype' && (
71
+ <div
72
+ className={cn(
73
+ 'absolute inset-0 lg:hidden opacity-10 dark:opacity-5',
74
+ props.gradientClassName ?? 'bg-[image:var(--gradient-diagonal)]'
75
+ )}
76
+ />
77
+ )}
33
78
  <div className="w-full max-w-sm space-y-6 relative z-10">{children}</div>
34
79
  </div>
35
80
  </div>
@@ -37,7 +37,7 @@ export function SocialLoginButtons({
37
37
  <Button
38
38
  type="button"
39
39
  variant="outline"
40
- className="w-full justify-center"
40
+ className="w-full justify-center shadow-xs"
41
41
  onClick={() => onSocialLogin('Google')}
42
42
  >
43
43
  <svg className="w-5 h-5 mr-2" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
@@ -67,7 +67,7 @@ export function SocialLoginButtons({
67
67
  <Button
68
68
  type="button"
69
69
  variant="outline"
70
- className="w-full justify-center"
70
+ className="w-full justify-center shadow-xs"
71
71
  onClick={() => onSocialLogin('MT Login')}
72
72
  >
73
73
  <Lock className="w-5 h-5 mr-2 text-[var(--chart-4)]" />
@@ -79,7 +79,7 @@ export function SocialLoginButtons({
79
79
  <Button
80
80
  type="button"
81
81
  variant="outline"
82
- className="w-full justify-center font-normal"
82
+ className="w-full justify-center font-normal shadow-xs"
83
83
  onClick={() => onSocialLogin('gov.br')}
84
84
  >
85
85
  <span>
@@ -1,2 +1,3 @@
1
1
  export * from './card-patterns';
2
2
  export * from './auth';
3
+ export * from './marketing';
@@ -0,0 +1,73 @@
1
+ import * as React from 'react';
2
+ import { Button } from '../../ui/button';
3
+ import { cn } from '../../shared/utils';
4
+
5
+ export interface CTASectionAction {
6
+ label: string;
7
+ href?: string;
8
+ onClick?: () => void;
9
+ }
10
+
11
+ export interface CTASectionProps extends React.HTMLAttributes<HTMLElement> {
12
+ title: string;
13
+ description?: string;
14
+ cta: CTASectionAction;
15
+ secondaryCta?: CTASectionAction;
16
+ }
17
+
18
+ /**
19
+ * Full-width dark band prompting a primary conversion action.
20
+ *
21
+ * @description
22
+ * A high-impact closing section for marketing pages: a bold left-aligned
23
+ * headline, optional supporting copy, and up to two CTA buttons on a flat
24
+ * `bg-sidebar` surface — the same token-driven dark surface used elsewhere
25
+ * on this page (e.g. the methodology section) — so it stays legible across
26
+ * all Xertica-ui themes without per-theme contrast tuning or a gradient.
27
+ *
28
+ * @ai-rules
29
+ * 1. The background is a flat `bg-sidebar` surface with a `border-sidebar-border` hairline — never reintroduce a gradient or hardcode colors here.
30
+ * 2. Both buttons use their natural variants (`default` for `cta`, `outline` for `secondaryCta`) with no color overrides — they already contrast correctly against `bg-sidebar`; do not force-style them with one-off colors.
31
+ * 3. Each `cta`/`secondaryCta` needs either `href` (renders an `<a>`) or `onClick`.
32
+ */
33
+ export function CTASection({
34
+ title,
35
+ description,
36
+ cta,
37
+ secondaryCta,
38
+ className,
39
+ ...props
40
+ }: CTASectionProps) {
41
+ return (
42
+ <section
43
+ className={cn(
44
+ 'w-full rounded-[var(--radius-card)] border border-sidebar-border bg-sidebar px-6 py-16 md:py-20',
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ <div className="flex flex-col gap-8 md:flex-row md:items-center md:justify-between">
50
+ <div className="max-w-xl">
51
+ <h2 className="text-2xl font-bold text-sidebar-foreground md:text-4xl">{title}</h2>
52
+ {description && <p className="mt-6 text-sidebar-foreground/70">{description}</p>}
53
+ </div>
54
+
55
+ <div className="flex flex-col gap-3 sm:flex-row md:shrink-0">
56
+ <Button asChild={!!cta.href} onClick={cta.onClick}>
57
+ {cta.href ? <a href={cta.href}>{cta.label}</a> : cta.label}
58
+ </Button>
59
+
60
+ {secondaryCta && (
61
+ <Button variant="outline" asChild={!!secondaryCta.href} onClick={secondaryCta.onClick}>
62
+ {secondaryCta.href ? (
63
+ <a href={secondaryCta.href}>{secondaryCta.label}</a>
64
+ ) : (
65
+ secondaryCta.label
66
+ )}
67
+ </Button>
68
+ )}
69
+ </div>
70
+ </div>
71
+ </section>
72
+ );
73
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../shared/utils';
3
+
4
+ export interface ComplianceBadge {
5
+ icon: React.ReactNode;
6
+ label: string;
7
+ description?: string;
8
+ }
9
+
10
+ export interface ComplianceBadgeRowProps extends React.HTMLAttributes<HTMLDivElement> {
11
+ badges: ComplianceBadge[];
12
+ }
13
+
14
+ /**
15
+ * A row of certification/compliance badges (e.g. SOC 2, GDPR, WCAG 2.1 AA).
16
+ *
17
+ * @description
18
+ * Renders a left-aligned grid where each badge pairs a token-radius outlined
19
+ * icon chip with a label and an optional short description, built entirely
20
+ * from theme tokens so it reskins across all Xertica-ui themes and dark mode.
21
+ *
22
+ * @ai-rules
23
+ * 1. Provide `badges` as `{ icon, label, description? }` — `icon` should be a small lucide-react element.
24
+ * 2. Keep `label` and `description` short — badges sit in a `grid-cols-2 sm:grid-cols-4` layout, so each column has limited width.
25
+ * 3. Do not hand-roll badge styling elsewhere — this component owns the icon-chip/label/description layout.
26
+ */
27
+ export function ComplianceBadgeRow({ badges, className, ...props }: ComplianceBadgeRowProps) {
28
+ return (
29
+ <div
30
+ className={cn('grid grid-cols-2 gap-x-8 gap-y-10 sm:grid-cols-4', className)}
31
+ {...props}
32
+ >
33
+ {badges.map((badge, index) => (
34
+ <div
35
+ key={`${badge.label}-${index}`}
36
+ className="flex flex-col items-start gap-3 text-left"
37
+ >
38
+ <div className="flex items-center justify-center rounded-[var(--radius)] border border-border bg-background p-3 [&>svg]:size-6">
39
+ {badge.icon}
40
+ </div>
41
+ <p className="text-sm font-medium text-foreground">{badge.label}</p>
42
+ {badge.description && (
43
+ <p className="text-xs text-muted-foreground">{badge.description}</p>
44
+ )}
45
+ </div>
46
+ ))}
47
+ </div>
48
+ );
49
+ }
@@ -0,0 +1,91 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../shared/utils';
3
+
4
+ export type BlockColor =
5
+ | 'primary'
6
+ | 'chart-1'
7
+ | 'chart-2'
8
+ | 'chart-3'
9
+ | 'chart-4'
10
+ | 'chart-5'
11
+ | 'success'
12
+ | 'info'
13
+ | 'warning'
14
+ | 'destructive';
15
+
16
+ const colorTokens: Record<BlockColor, { bg: string; icon: string }> = {
17
+ primary: { bg: 'bg-primary/10', icon: 'text-primary' },
18
+ 'chart-1': { bg: 'bg-[var(--chart-1)]/15', icon: 'text-[var(--chart-1)]' },
19
+ 'chart-2': { bg: 'bg-[var(--chart-2)]/15', icon: 'text-[var(--chart-2)]' },
20
+ 'chart-3': { bg: 'bg-[var(--chart-3)]/15', icon: 'text-[var(--chart-3)]' },
21
+ 'chart-4': { bg: 'bg-[var(--chart-4)]/15', icon: 'text-[var(--chart-4)]' },
22
+ 'chart-5': { bg: 'bg-[var(--chart-5)]/15', icon: 'text-[var(--chart-5)]' },
23
+ success: { bg: 'bg-success/10', icon: 'text-success' },
24
+ info: { bg: 'bg-info/10', icon: 'text-info' },
25
+ warning: { bg: 'bg-warning/10', icon: 'text-warning' },
26
+ destructive: { bg: 'bg-destructive/10', icon: 'text-destructive' },
27
+ };
28
+
29
+ export interface EvidenceTimelineItem {
30
+ number?: string | number;
31
+ icon?: React.ReactNode;
32
+ title: string;
33
+ description: string;
34
+ color?: BlockColor;
35
+ }
36
+
37
+ export interface EvidenceTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
38
+ items: EvidenceTimelineItem[];
39
+ }
40
+
41
+ /**
42
+ * Vertical numbered timeline with a connecting line and colored step dots.
43
+ *
44
+ * @description
45
+ * Renders a "how our methodology works" style list: each item gets a
46
+ * colored dot (icon or step number) on a shared vertical line, with a
47
+ * title and description to its right.
48
+ *
49
+ * @ai-rules
50
+ * 1. Provide `icon` for a lucide-react icon in the dot, or `number` for a step label; if neither is set the 1-based index is used.
51
+ * 2. Each item's `color` maps to `colorTokens` — never hardcode colors.
52
+ * 3. This block has no outer card chrome; wrap it in a `<Card>` or section if chrome is needed.
53
+ */
54
+ export function EvidenceTimeline({ items, className, ...props }: EvidenceTimelineProps) {
55
+ return (
56
+ <div className={cn('relative pl-12', className)} {...props}>
57
+ <div aria-hidden="true" className="absolute left-4 top-4 bottom-4 w-px bg-border" />
58
+
59
+ <ol className="relative">
60
+ {items.map((item, index) => {
61
+ const { bg, icon: iconColor } = colorTokens[item.color ?? 'primary'];
62
+
63
+ return (
64
+ <li key={index} className="relative flex items-start gap-4 pb-10 last:pb-0">
65
+ <div
66
+ className={cn(
67
+ 'absolute -left-12 top-0 flex size-8 shrink-0 items-center justify-center rounded-[var(--radius)] z-10 border border-current/20',
68
+ bg
69
+ )}
70
+ >
71
+ <span
72
+ className={cn(
73
+ 'flex items-center justify-center text-sm font-semibold [&>svg]:size-4',
74
+ iconColor
75
+ )}
76
+ >
77
+ {item.icon ?? (item.number ?? index + 1)}
78
+ </span>
79
+ </div>
80
+
81
+ <div className="min-w-0">
82
+ <p className="font-semibold">{item.title}</p>
83
+ <p className="mt-2 text-sm opacity-70">{item.description}</p>
84
+ </div>
85
+ </li>
86
+ );
87
+ })}
88
+ </ol>
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,78 @@
1
+ import * as React from 'react';
2
+ import { CheckCircle2 } from 'lucide-react';
3
+ import { cn } from '../../shared/utils';
4
+
5
+ export interface FeatureRowProps extends React.HTMLAttributes<HTMLElement> {
6
+ eyebrow?: string;
7
+ title: string;
8
+ description?: string;
9
+ bullets?: string[];
10
+ visual: React.ReactNode;
11
+ reverse?: boolean;
12
+ tone?: 'default' | 'muted' | 'card';
13
+ }
14
+
15
+ /**
16
+ * Alternating text + visual marketing section.
17
+ *
18
+ * @description
19
+ * A two-column "how it works" / definition row: a text block (eyebrow,
20
+ * title, description, optional checklist) paired with a consumer-supplied
21
+ * visual (image, illustration, or custom node). Stacks on mobile and
22
+ * alternates sides on desktop via `reverse`.
23
+ *
24
+ * @ai-rules
25
+ * 1. Chain multiple `FeatureRow`s and alternate `reverse` to build a "how it works" narrative.
26
+ * 2. Pass the visual as `<ImageWithFallback />` or any custom `ReactNode`; this component never renders an image itself.
27
+ * 3. Use `tone="muted"` on every other row to create a subtle section break instead of adding manual dividers; use `tone="card"` when a theme needs a stronger, genuinely lighter break (some themes set `--muted` equal to `--background`).
28
+ */
29
+ export function FeatureRow({
30
+ eyebrow,
31
+ title,
32
+ description,
33
+ bullets,
34
+ visual,
35
+ reverse = false,
36
+ tone = 'default',
37
+ className,
38
+ ...props
39
+ }: FeatureRowProps) {
40
+ return (
41
+ <section
42
+ className={cn(
43
+ 'py-16 md:py-24',
44
+ tone === 'muted' ? 'bg-muted' : tone === 'card' ? 'bg-card' : 'bg-background',
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ <div className="mx-auto grid max-w-6xl gap-12 px-4 sm:px-6 lg:grid-cols-2 lg:items-center lg:px-8">
50
+ <div className={cn(reverse ? 'lg:order-last' : 'lg:order-first')}>
51
+ {eyebrow && (
52
+ <p className="text-sm font-semibold uppercase tracking-wide text-primary">
53
+ {eyebrow}
54
+ </p>
55
+ )}
56
+ <h2 className={cn('text-2xl font-bold text-foreground md:text-3xl', eyebrow && 'mt-2')}>
57
+ {title}
58
+ </h2>
59
+ {description && (
60
+ <p className="mt-6 text-muted-foreground leading-relaxed">{description}</p>
61
+ )}
62
+ {bullets && bullets.length > 0 && (
63
+ <ul className="mt-4 space-y-2">
64
+ {bullets.map(bullet => (
65
+ <li key={bullet} className="flex items-start gap-2">
66
+ <CheckCircle2 className="size-5 shrink-0 text-primary" />
67
+ <span className="text-foreground">{bullet}</span>
68
+ </li>
69
+ ))}
70
+ </ul>
71
+ )}
72
+ </div>
73
+
74
+ <div className={cn(reverse ? 'lg:order-first' : 'lg:order-last')}>{visual}</div>
75
+ </div>
76
+ </section>
77
+ );
78
+ }
@@ -0,0 +1,125 @@
1
+ import * as React from 'react';
2
+ import { Button } from '../../ui/button';
3
+ import { PillBadge } from './PillBadge';
4
+ import { cn } from '../../shared/utils';
5
+
6
+ export interface HeroCta {
7
+ label: string;
8
+ href?: string;
9
+ onClick?: () => void;
10
+ }
11
+
12
+ export interface HeroProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title'> {
13
+ eyebrow?: { label: string; icon?: React.ReactNode };
14
+ title: React.ReactNode;
15
+ description?: React.ReactNode;
16
+ primaryCta?: HeroCta;
17
+ secondaryCta?: HeroCta;
18
+ visual?: React.ReactNode;
19
+ align?: 'left' | 'center';
20
+ decorative?: boolean;
21
+ }
22
+
23
+ function HeroCtaButton({
24
+ cta,
25
+ variant,
26
+ }: {
27
+ cta: HeroCta;
28
+ variant: React.ComponentProps<typeof Button>['variant'];
29
+ }) {
30
+ if (cta.href) {
31
+ return (
32
+ <Button asChild variant={variant} size="lg">
33
+ <a href={cta.href} onClick={cta.onClick}>
34
+ {cta.label}
35
+ </a>
36
+ </Button>
37
+ );
38
+ }
39
+
40
+ return (
41
+ <Button variant={variant} size="lg" onClick={cta.onClick}>
42
+ {cta.label}
43
+ </Button>
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Full-width, above-the-fold marketing hero section.
49
+ *
50
+ * @description
51
+ * The lead block for landing and marketing pages: an optional `PillBadge`
52
+ * eyebrow, a very large headline, supporting copy, and up to two `<Button>`
53
+ * CTAs. Automatically switches from a single column to a two-column layout
54
+ * once a `visual` (screenshot, illustration, collage) is supplied.
55
+ *
56
+ * @ai-rules
57
+ * 1. Pass `visual` for the two-column desktop layout; omit it for a single-column hero.
58
+ * 2. `primaryCta` renders as `default` (primary/brand) and `secondaryCta` as `secondary`, both via `<Button>` (`size="lg"`) — never hand-roll buttons.
59
+ * 3. Set `decorative` to add a token-based gradient blob backdrop; it is purely visual and never shifts layout.
60
+ * 4. `align="center"` centers the eyebrow/heading/copy/CTAs — pair it with omitting `visual` for a classic centered hero.
61
+ */
62
+ export function Hero({
63
+ eyebrow,
64
+ title,
65
+ description,
66
+ primaryCta,
67
+ secondaryCta,
68
+ visual,
69
+ align = 'left',
70
+ decorative = false,
71
+ className,
72
+ ...props
73
+ }: HeroProps) {
74
+ const isCentered = align === 'center';
75
+
76
+ return (
77
+ <section className={cn('relative overflow-hidden py-16 md:py-24', className)} {...props}>
78
+ {decorative && (
79
+ <div
80
+ className="pointer-events-none absolute inset-0 z-0 overflow-hidden"
81
+ aria-hidden="true"
82
+ >
83
+ <div className="absolute left-1/2 top-1/2 h-72 w-72 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[linear-gradient(135deg,var(--gradient-start),var(--gradient-end))] opacity-20 blur-3xl md:h-[28rem] md:w-[28rem] lg:h-[36rem] lg:w-[36rem]" />
84
+ </div>
85
+ )}
86
+
87
+ <div className="relative z-10 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
88
+ <div
89
+ className={cn('grid items-center gap-12', visual ? 'lg:grid-cols-2' : 'mx-auto max-w-3xl')}
90
+ >
91
+ <div
92
+ className={cn(
93
+ 'flex flex-col gap-6',
94
+ isCentered ? 'items-center text-center' : 'items-start text-left'
95
+ )}
96
+ >
97
+ {eyebrow && <PillBadge icon={eyebrow.icon}>{eyebrow.label}</PillBadge>}
98
+
99
+ <h1 className="text-4xl font-bold tracking-tight text-foreground md:text-5xl lg:text-6xl">
100
+ {title}
101
+ </h1>
102
+
103
+ {description && (
104
+ <p className="max-w-2xl text-lg text-muted-foreground">{description}</p>
105
+ )}
106
+
107
+ {(primaryCta || secondaryCta) && (
108
+ <div
109
+ className={cn(
110
+ 'flex flex-col gap-3 pt-2 sm:flex-row',
111
+ isCentered && 'sm:justify-center'
112
+ )}
113
+ >
114
+ {primaryCta && <HeroCtaButton cta={primaryCta} variant="default" />}
115
+ {secondaryCta && <HeroCtaButton cta={secondaryCta} variant="secondary" />}
116
+ </div>
117
+ )}
118
+ </div>
119
+
120
+ {visual && <div className="relative">{visual}</div>}
121
+ </div>
122
+ </div>
123
+ </section>
124
+ );
125
+ }