create-snapify-app 0.0.0-stage → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (614) hide show
  1. package/LICENSE.md +24 -0
  2. package/README.md +55 -2
  3. package/bin/create-snapify-app.mjs +174 -0
  4. package/lib/args.mjs +59 -0
  5. package/lib/copy.mjs +44 -0
  6. package/lib/env.mjs +84 -0
  7. package/lib/prompt.mjs +63 -0
  8. package/package.json +35 -4
  9. package/template/.env.example +79 -0
  10. package/template/.github/workflows/ci.yml +23 -0
  11. package/template/.github/workflows/deploy.yml +26 -0
  12. package/template/.github/workflows/ping.yml +8 -0
  13. package/template/.github/workflows/release.yml +67 -0
  14. package/template/.snapify-install.json +5 -0
  15. package/template/CHANGELOG.md +28 -0
  16. package/template/LICENSE +14 -0
  17. package/template/README.md +287 -0
  18. package/template/administrator.html +23 -0
  19. package/template/apps/client/src/App.tsx +765 -0
  20. package/template/apps/client/src/auth.css +400 -0
  21. package/template/apps/client/src/lib/api.ts +22 -0
  22. package/template/apps/client/src/main.tsx +20 -0
  23. package/template/apps/community/src/App.tsx +592 -0
  24. package/template/apps/community/src/main.tsx +20 -0
  25. package/template/apps/community/src/styles.css +24 -0
  26. package/template/apps/developers/src/App.tsx +643 -0
  27. package/template/apps/developers/src/main.tsx +17 -0
  28. package/template/apps/developers/src/pages.tsx +1587 -0
  29. package/template/apps/developers/src/styles.css +98 -0
  30. package/template/apps/developers/src/ui.tsx +179 -0
  31. package/template/apps/landing/src/App.tsx +598 -0
  32. package/template/apps/landing/src/LegalPages.tsx +158 -0
  33. package/template/apps/landing/src/OrderPage.tsx +381 -0
  34. package/template/apps/landing/src/SeoPages.tsx +923 -0
  35. package/template/apps/landing/src/copy.ts +195 -0
  36. package/template/apps/landing/src/main.tsx +20 -0
  37. package/template/apps/landing/src/styles.css +803 -0
  38. package/template/apps/landing/src/types.ts +19 -0
  39. package/template/apps/marketplace/src/App.tsx +1229 -0
  40. package/template/apps/marketplace/src/developer/DeveloperApp.tsx +1266 -0
  41. package/template/apps/marketplace/src/main.tsx +20 -0
  42. package/template/apps/marketplace/src/styles.css +1001 -0
  43. package/template/apps/shared/AuthContext.tsx +65 -0
  44. package/template/apps/shared/Logo.tsx +33 -0
  45. package/template/apps/shared/api.ts +47 -0
  46. package/template/apps/shared/ui.tsx +128 -0
  47. package/template/apps/superadmin/src/App.tsx +1264 -0
  48. package/template/apps/superadmin/src/lib/api.ts +20 -0
  49. package/template/apps/superadmin/src/lib/hooks.ts +20 -0
  50. package/template/apps/superadmin/src/main.tsx +16 -0
  51. package/template/apps/superadmin/src/pages/ClientsPage.tsx +185 -0
  52. package/template/apps/superadmin/src/pages/InvoicesPage.tsx +157 -0
  53. package/template/apps/superadmin/src/pages/NewsletterPage.tsx +212 -0
  54. package/template/apps/superadmin/src/pages/OrdersPage.tsx +195 -0
  55. package/template/apps/superadmin/src/pages/PaymentsPage.tsx +150 -0
  56. package/template/apps/superadmin/src/pages/PayoutsPage.tsx +133 -0
  57. package/template/apps/superadmin/src/pages/PublishersPage.tsx +151 -0
  58. package/template/apps/superadmin/src/pages/SeoControlPage.tsx +692 -0
  59. package/template/apps/superadmin/src/pages/SettingsPage.tsx +199 -0
  60. package/template/apps/superadmin/src/pages/SystemPage.tsx +109 -0
  61. package/template/apps/superadmin/src/pages/TicketsPage.tsx +201 -0
  62. package/template/apps/superadmin/src/pages/UsersPage.tsx +167 -0
  63. package/template/client.html +21 -0
  64. package/template/community/hamdiayari/akhbar/README.md +23 -0
  65. package/template/community/hamdiayari/akhbar/screenshot.jpg +0 -0
  66. package/template/community/hamdiayari/akhbar/snapify.theme.json +76 -0
  67. package/template/community/hamdiayari/akhbar/src/Brand.tsx +26 -0
  68. package/template/community/hamdiayari/akhbar/src/Footer.tsx +182 -0
  69. package/template/community/hamdiayari/akhbar/src/Header.tsx +191 -0
  70. package/template/community/hamdiayari/akhbar/src/Layout.tsx +49 -0
  71. package/template/community/hamdiayari/akhbar/src/Social.tsx +33 -0
  72. package/template/community/hamdiayari/akhbar/src/Ticker.tsx +86 -0
  73. package/template/community/hamdiayari/akhbar/src/Views.tsx +285 -0
  74. package/template/community/hamdiayari/akhbar/src/blocks/CollectionBlocks.tsx +672 -0
  75. package/template/community/hamdiayari/akhbar/src/blocks/ContentBlocks.tsx +305 -0
  76. package/template/community/hamdiayari/akhbar/src/blocks/ConversionBlocks.tsx +569 -0
  77. package/template/community/hamdiayari/akhbar/src/blocks/HeaderBlocks.tsx +287 -0
  78. package/template/community/hamdiayari/akhbar/src/index.tsx +51 -0
  79. package/template/community/hamdiayari/akhbar/src/kit.tsx +357 -0
  80. package/template/community/hamdiayari/akhbar/src/site.ts +96 -0
  81. package/template/community/hamdiayari/akhbar/src/styles.css +566 -0
  82. package/template/community/hamdiayari/akhbar-actualites/README.md +28 -0
  83. package/template/community/hamdiayari/akhbar-actualites/screenshot.jpg +0 -0
  84. package/template/community/hamdiayari/akhbar-actualites/snapify.module.json +107 -0
  85. package/template/community/hamdiayari/akhbar-actualites/src/admin/AudiencePage.tsx +185 -0
  86. package/template/community/hamdiayari/akhbar-actualites/src/admin/index.tsx +11 -0
  87. package/template/community/hamdiayari/akhbar-actualites/src/public/Archive.tsx +142 -0
  88. package/template/community/hamdiayari/akhbar-actualites/src/public/cards.tsx +152 -0
  89. package/template/community/hamdiayari/akhbar-actualites/src/public/index.tsx +71 -0
  90. package/template/community/hamdiayari/akhbar-actualites/src/public/posts.ts +103 -0
  91. package/template/community/hamdiayari/akhbar-actualites/src/public/styles.css +568 -0
  92. package/template/community/hamdiayari/akhbar-actualites/src/public/widgets.tsx +226 -0
  93. package/template/community/hamdiayari/akhbar-actualites/src/server.ts +119 -0
  94. package/template/community/hamdiayari/akhbar-actualites/src/types.ts +25 -0
  95. package/template/community/hamdiayari/akhbar-urgent/README.md +36 -0
  96. package/template/community/hamdiayari/akhbar-urgent/screenshot.jpg +0 -0
  97. package/template/community/hamdiayari/akhbar-urgent/snapify.module.json +64 -0
  98. package/template/community/hamdiayari/akhbar-urgent/src/admin/FlashesPage.tsx +229 -0
  99. package/template/community/hamdiayari/akhbar-urgent/src/admin/index.tsx +11 -0
  100. package/template/community/hamdiayari/akhbar-urgent/src/public/Ticker.tsx +81 -0
  101. package/template/community/hamdiayari/akhbar-urgent/src/public/index.tsx +63 -0
  102. package/template/community/hamdiayari/akhbar-urgent/src/public/styles.css +142 -0
  103. package/template/community/hamdiayari/akhbar-urgent/src/server.ts +101 -0
  104. package/template/community/hamdiayari/akhbar-urgent/src/types.ts +35 -0
  105. package/template/community.html +21 -0
  106. package/template/developers.html +24 -0
  107. package/template/docker-compose.db.yml +24 -0
  108. package/template/docs/BACKOFFICE_SNAPIFY.md +130 -0
  109. package/template/docs/CI_CD.md +66 -0
  110. package/template/docs/MODULES.md +346 -0
  111. package/template/docs/PLATFORM_DEPLOY.md +54 -0
  112. package/template/docs/logo.svg +10 -0
  113. package/template/drizzle/0000_init.sql +147 -0
  114. package/template/drizzle/0001_page_project.sql +5 -0
  115. package/template/drizzle/0002_modules.sql +23 -0
  116. package/template/drizzle/0003_themes.sql +4 -0
  117. package/template/drizzle/0004_security.sql +82 -0
  118. package/template/drizzle/0005_module_records.sql +16 -0
  119. package/template/drizzle/0006_avatars.sql +4 -0
  120. package/template/drizzle/0007_platform.sql +264 -0
  121. package/template/drizzle/0008_mp_validation.sql +3 -0
  122. package/template/drizzle/0009_saas_ops.sql +77 -0
  123. package/template/drizzle/0010_cms_chrome.sql +13 -0
  124. package/template/drizzle/0011_client_portal.sql +26 -0
  125. package/template/drizzle/meta/0000_snapshot.json +998 -0
  126. package/template/drizzle/meta/0001_snapshot.json +1004 -0
  127. package/template/drizzle/meta/_journal.json +90 -0
  128. package/template/drizzle.config.ts +16 -0
  129. package/template/ecosystem.snapify.cjs +24 -0
  130. package/template/for.html +1 -0
  131. package/template/index.html +29 -0
  132. package/template/landing.html +37 -0
  133. package/template/marketplace/catalog.json +373 -0
  134. package/template/marketplace/gallery/hamdiayari.preveris/01-home.jpg +0 -0
  135. package/template/marketplace/gallery/hamdiayari.recaptcha/01-reglages.jpg +0 -0
  136. package/template/marketplace/gallery/hamdiayari.recaptcha/02-logo.jpg +0 -0
  137. package/template/marketplace/gallery/novalabs.ai-articles/01-generer.jpg +0 -0
  138. package/template/marketplace/gallery/novalabs.ai-articles/02-fournisseur-ia.jpg +0 -0
  139. package/template/marketplace/gallery/novalabs.calendar/01-agenda-site.jpg +0 -0
  140. package/template/marketplace/gallery/novalabs.calendar/02-evenements-admin.jpg +0 -0
  141. package/template/marketplace/gallery/novalabs.calendar/03-reglages.jpg +0 -0
  142. package/template/marketplace/screenshots/hamdiayari.preveris-1.0.6.jpg +0 -0
  143. package/template/marketplace/screenshots/hamdiayari.recaptcha-1.0.0.jpg +0 -0
  144. package/template/marketplace/screenshots/hamdiayari.wp-seo-migrator-1.1.1.jpg +0 -0
  145. package/template/marketplace/screenshots/novalabs.ai-articles-1.0.1.jpg +0 -0
  146. package/template/marketplace/screenshots/novalabs.ai-chat-1.0.1.jpg +0 -0
  147. package/template/marketplace/screenshots/novalabs.calendar-1.0.1.jpg +0 -0
  148. package/template/marketplace/screenshots/novalabs.cart-1.0.0.jpg +0 -0
  149. package/template/marketplace/screenshots/novalabs.checkout-1.0.0.jpg +0 -0
  150. package/template/marketplace/screenshots/novalabs.customers-1.1.0.jpg +0 -0
  151. package/template/marketplace/screenshots/novalabs.dogga-1.0.0.jpg +0 -0
  152. package/template/marketplace/screenshots/novalabs.mahdia-1.0.0.jpg +0 -0
  153. package/template/marketplace/screenshots/novalabs.shop-1.0.0.jpg +0 -0
  154. package/template/marketplace/screenshots/novalabs.sidibou-1.0.0.jpg +0 -0
  155. package/template/marketplace/screenshots/novalabs.whatsapp-1.0.1.jpg +0 -0
  156. package/template/marketplace.html +21 -0
  157. package/template/metadata.json +7 -0
  158. package/template/modules/ai-articles/screenshot.jpg +0 -0
  159. package/template/modules/ai-articles/snapify.module.json +74 -0
  160. package/template/modules/ai-articles/src/admin/ArticlesPage.tsx +162 -0
  161. package/template/modules/ai-articles/src/admin/GeneratePage.tsx +118 -0
  162. package/template/modules/ai-articles/src/admin/index.tsx +12 -0
  163. package/template/modules/ai-articles/src/public/index.tsx +119 -0
  164. package/template/modules/ai-articles/src/server.ts +287 -0
  165. package/template/modules/ai-articles/src/types.ts +30 -0
  166. package/template/modules/ai-chat/screenshot.jpg +0 -0
  167. package/template/modules/ai-chat/snapify.module.json +217 -0
  168. package/template/modules/ai-chat/src/public.tsx +198 -0
  169. package/template/modules/ai-chat/src/server.ts +142 -0
  170. package/template/modules/ai-theme-studio/README.md +15 -0
  171. package/template/modules/ai-theme-studio/screenshot.jpg +0 -0
  172. package/template/modules/ai-theme-studio/snapify.module.json +175 -0
  173. package/template/modules/ai-theme-studio/src/admin/HistoryPage.tsx +91 -0
  174. package/template/modules/ai-theme-studio/src/admin/StudioPage.tsx +414 -0
  175. package/template/modules/ai-theme-studio/src/admin/index.tsx +11 -0
  176. package/template/modules/ai-theme-studio/src/lib/images.ts +240 -0
  177. package/template/modules/ai-theme-studio/src/lib/llm.ts +123 -0
  178. package/template/modules/ai-theme-studio/src/lib/normalize.ts +714 -0
  179. package/template/modules/ai-theme-studio/src/lib/prompts.ts +198 -0
  180. package/template/modules/ai-theme-studio/src/server.ts +137 -0
  181. package/template/modules/ai-theme-studio/src/types.ts +112 -0
  182. package/template/modules/calendar/screenshot.jpg +0 -0
  183. package/template/modules/calendar/snapify.module.json +45 -0
  184. package/template/modules/calendar/src/admin/EventsPage.tsx +217 -0
  185. package/template/modules/calendar/src/admin/index.tsx +11 -0
  186. package/template/modules/calendar/src/public/index.tsx +231 -0
  187. package/template/modules/calendar/src/server.ts +143 -0
  188. package/template/modules/calendar/src/types.ts +25 -0
  189. package/template/modules/cart/screenshot.jpg +0 -0
  190. package/template/modules/cart/snapify.module.json +90 -0
  191. package/template/modules/cart/src/admin/index.tsx +265 -0
  192. package/template/modules/cart/src/public/CartPage.tsx +198 -0
  193. package/template/modules/cart/src/public/index.tsx +261 -0
  194. package/template/modules/cart/src/public/useQuote.ts +67 -0
  195. package/template/modules/cart/src/server.ts +148 -0
  196. package/template/modules/cart/src/types.ts +31 -0
  197. package/template/modules/checkout/screenshot.jpg +0 -0
  198. package/template/modules/checkout/snapify.module.json +264 -0
  199. package/template/modules/checkout/src/admin/OrderDetailPage.tsx +304 -0
  200. package/template/modules/checkout/src/admin/badges.tsx +28 -0
  201. package/template/modules/checkout/src/admin/index.tsx +236 -0
  202. package/template/modules/checkout/src/public/CheckoutPage.tsx +461 -0
  203. package/template/modules/checkout/src/public/OrderPages.tsx +153 -0
  204. package/template/modules/checkout/src/public/index.tsx +23 -0
  205. package/template/modules/checkout/src/server.ts +383 -0
  206. package/template/modules/checkout/src/types.ts +70 -0
  207. package/template/modules/customers/screenshot.jpg +0 -0
  208. package/template/modules/customers/snapify.module.json +115 -0
  209. package/template/modules/customers/src/admin/CustomerDetailPage.tsx +274 -0
  210. package/template/modules/customers/src/admin/index.tsx +194 -0
  211. package/template/modules/customers/src/public/AccountPages.tsx +470 -0
  212. package/template/modules/customers/src/public/AddressForm.tsx +115 -0
  213. package/template/modules/customers/src/public/AuthPages.tsx +349 -0
  214. package/template/modules/customers/src/public/Avatar.tsx +29 -0
  215. package/template/modules/customers/src/public/ProfileSection.tsx +291 -0
  216. package/template/modules/customers/src/public/index.tsx +137 -0
  217. package/template/modules/customers/src/public/session.ts +51 -0
  218. package/template/modules/customers/src/server.ts +421 -0
  219. package/template/modules/customers/src/types.ts +53 -0
  220. package/template/modules/recaptcha/logo.jpg +0 -0
  221. package/template/modules/recaptcha/screenshot.jpg +0 -0
  222. package/template/modules/recaptcha/snapify.module.json +99 -0
  223. package/template/modules/recaptcha/src/public/index.tsx +94 -0
  224. package/template/modules/recaptcha/src/server.ts +65 -0
  225. package/template/modules/recaptcha/src/types.ts +18 -0
  226. package/template/modules/shop/screenshot.jpg +0 -0
  227. package/template/modules/shop/snapify.module.json +340 -0
  228. package/template/modules/shop/src/admin/CategoriesPage.tsx +224 -0
  229. package/template/modules/shop/src/admin/ProductEditor.tsx +313 -0
  230. package/template/modules/shop/src/admin/ProductsPage.tsx +307 -0
  231. package/template/modules/shop/src/admin/index.tsx +14 -0
  232. package/template/modules/shop/src/public/Catalog.tsx +244 -0
  233. package/template/modules/shop/src/public/NotFound.tsx +22 -0
  234. package/template/modules/shop/src/public/ProductPage.tsx +195 -0
  235. package/template/modules/shop/src/public/index.tsx +134 -0
  236. package/template/modules/shop/src/public/useFetch.ts +26 -0
  237. package/template/modules/shop/src/server.ts +493 -0
  238. package/template/modules/shop/src/shared.ts +41 -0
  239. package/template/modules/shop/src/types.ts +60 -0
  240. package/template/modules/whatsapp/screenshot.jpg +0 -0
  241. package/template/modules/whatsapp/snapify.module.json +121 -0
  242. package/template/modules/whatsapp/src/public.tsx +119 -0
  243. package/template/modules/wp-seo-migrator/ARCHITECTURE.md +113 -0
  244. package/template/modules/wp-seo-migrator/README.md +21 -0
  245. package/template/modules/wp-seo-migrator/fixtures/aioseo-sample.csv +3 -0
  246. package/template/modules/wp-seo-migrator/fixtures/sample-exporter.json +39 -0
  247. package/template/modules/wp-seo-migrator/fixtures/sample-page.html +12 -0
  248. package/template/modules/wp-seo-migrator/fixtures/sample.htaccess +3 -0
  249. package/template/modules/wp-seo-migrator/screenshot.jpg +0 -0
  250. package/template/modules/wp-seo-migrator/snapify.module.json +87 -0
  251. package/template/modules/wp-seo-migrator/src/admin/HistoryPage.tsx +64 -0
  252. package/template/modules/wp-seo-migrator/src/admin/WizardPage.tsx +788 -0
  253. package/template/modules/wp-seo-migrator/src/admin/i18n.ts +156 -0
  254. package/template/modules/wp-seo-migrator/src/admin/index.tsx +11 -0
  255. package/template/modules/wp-seo-migrator/src/connectors/CUSTOM_CONNECTOR.md +31 -0
  256. package/template/modules/wp-seo-migrator/src/connectors/csvSeoFactory.ts +106 -0
  257. package/template/modules/wp-seo-migrator/src/connectors/exporter.ts +96 -0
  258. package/template/modules/wp-seo-migrator/src/connectors/gscCsv.ts +53 -0
  259. package/template/modules/wp-seo-migrator/src/connectors/htaccess.ts +65 -0
  260. package/template/modules/wp-seo-migrator/src/connectors/htmlCrawler.ts +90 -0
  261. package/template/modules/wp-seo-migrator/src/connectors/index.ts +113 -0
  262. package/template/modules/wp-seo-migrator/src/connectors/pluginCsv.ts +77 -0
  263. package/template/modules/wp-seo-migrator/src/connectors/rankMathCsv.ts +77 -0
  264. package/template/modules/wp-seo-migrator/src/connectors/redirection.ts +62 -0
  265. package/template/modules/wp-seo-migrator/src/connectors/sitemap.ts +44 -0
  266. package/template/modules/wp-seo-migrator/src/connectors/utils.ts +111 -0
  267. package/template/modules/wp-seo-migrator/src/connectors/wxr.ts +103 -0
  268. package/template/modules/wp-seo-migrator/src/connectors/yoastCsv.ts +56 -0
  269. package/template/modules/wp-seo-migrator/src/pipeline/analyze.ts +158 -0
  270. package/template/modules/wp-seo-migrator/src/pipeline/htmlParse.ts +58 -0
  271. package/template/modules/wp-seo-migrator/src/pipeline/remoteAuto.ts +328 -0
  272. package/template/modules/wp-seo-migrator/src/server.ts +492 -0
  273. package/template/modules/wp-seo-migrator/src/types.ts +151 -0
  274. package/template/modules/wp-seo-migrator/tests/connectors-extra.test.ts +39 -0
  275. package/template/modules/wp-seo-migrator/tests/merge.test.ts +25 -0
  276. package/template/package-lock.json +6016 -0
  277. package/template/package.json +74 -0
  278. package/template/public/images/photos/caravane-chameaux-desert.webp +0 -0
  279. package/template/public/images/photos/hammamet-medina-plage.jpg +0 -0
  280. package/template/public/images/photos/maison-campagne-collines.webp +0 -0
  281. package/template/public/images/photos/maisons-troglodytes-desert.jpg +0 -0
  282. package/template/public/images/photos/panorama-ville.jpg +0 -0
  283. package/template/public/images/photos/preveris-accessibilite-erp.jpg +0 -0
  284. package/template/public/images/photos/preveris-collectivites.jpg +0 -0
  285. package/template/public/images/photos/preveris-etudes-plans.jpg +0 -0
  286. package/template/public/images/photos/preveris-hero-erp.jpg +0 -0
  287. package/template/public/images/photos/preveris-ssi-incendie.jpg +0 -0
  288. package/template/public/images/photos/preveris-suivi-hotel.jpg +0 -0
  289. package/template/public/images/photos/ruelle-bleue-medina.jpg +0 -0
  290. package/template/public/images/photos/sidi-bou-said-port.webp +0 -0
  291. package/template/public/images/photos/terrasse-blanche-mer-voilier.jpg +0 -0
  292. package/template/public/landing/hero-ai-cms.jpg +0 -0
  293. package/template/public/landing/shot-dogga.jpg +0 -0
  294. package/template/public/landing/shot-mahdia.jpg +0 -0
  295. package/template/public/landing/shot-sidibou.jpg +0 -0
  296. package/template/public/llms.txt +35 -0
  297. package/template/public/logo.svg +10 -0
  298. package/template/scripts/app-pack.ts +155 -0
  299. package/template/scripts/app-publish.ts +123 -0
  300. package/template/scripts/apply-ecommerce-home.ts +196 -0
  301. package/template/scripts/auto-deploy-poll.sh +13 -0
  302. package/template/scripts/db-backup.sh +16 -0
  303. package/template/scripts/db-restore.sh +11 -0
  304. package/template/scripts/deploy.sh +34 -0
  305. package/template/scripts/github-webhook-deploy.sh +7 -0
  306. package/template/scripts/install-new-modules.ts +96 -0
  307. package/template/scripts/refresh-demo-baseline.ts +9 -0
  308. package/template/scripts/reinstall-marketplace-packs.ts +51 -0
  309. package/template/scripts/restore-classic-home.ts +13 -0
  310. package/template/scripts/sdk-pack.ts +289 -0
  311. package/template/scripts/sdk-publish.ts +75 -0
  312. package/template/scripts/seed-demo-products.ts +120 -0
  313. package/template/scripts/seed-theme-content-packs.ts +42 -0
  314. package/template/scripts/snapify-module.ts +207 -0
  315. package/template/scripts/test-theme-switch.ts +24 -0
  316. package/template/sdk/admin.css +18 -0
  317. package/template/sdk/admin.ts +95 -0
  318. package/template/sdk/build.ts +249 -0
  319. package/template/sdk/commerce.ts +512 -0
  320. package/template/sdk/content.ts +76 -0
  321. package/template/sdk/icons.ts +101 -0
  322. package/template/sdk/image.ts +60 -0
  323. package/template/sdk/index.ts +500 -0
  324. package/template/sdk/package.ts +298 -0
  325. package/template/sdk/recaptcha.tsx +122 -0
  326. package/template/sdk/settings.ts +84 -0
  327. package/template/sdk/theme.css +29 -0
  328. package/template/sdk/theme.ts +100 -0
  329. package/template/sdk/ui/commerce.tsx +385 -0
  330. package/template/sdk/ui/orders.tsx +136 -0
  331. package/template/sdk-kit/CHANGELOG.md +17 -0
  332. package/template/sdk-kit/LICENSE.md +47 -0
  333. package/template/sdk-kit/README.md +81 -0
  334. package/template/sdk-kit/cli.ts +339 -0
  335. package/template/sdk-kit/create-snapify/README.md +28 -0
  336. package/template/sdk-kit/create-snapify/create-snapify.mjs +11 -0
  337. package/template/sdk-kit/create-snapify/package.template.json +16 -0
  338. package/template/sdk-kit/package.template.json +95 -0
  339. package/template/sdk-kit/templates/common/README.md.tpl +27 -0
  340. package/template/sdk-kit/templates/common/_gitignore +3 -0
  341. package/template/sdk-kit/templates/common/package.json.tpl +19 -0
  342. package/template/sdk-kit/templates/common/src/snapify-env.d.ts +2 -0
  343. package/template/sdk-kit/templates/common/tsconfig.json +15 -0
  344. package/template/sdk-kit/templates/module/snapify.module.json.tpl +33 -0
  345. package/template/sdk-kit/templates/module/src/admin.tsx +53 -0
  346. package/template/sdk-kit/templates/module/src/public.tsx +49 -0
  347. package/template/sdk-kit/templates/module/src/server.ts +30 -0
  348. package/template/sdk-kit/templates/module/src/types.ts +19 -0
  349. package/template/sdk-kit/templates/theme/snapify.theme.json.tpl +22 -0
  350. package/template/sdk-kit/templates/theme/src/Footer.tsx +40 -0
  351. package/template/sdk-kit/templates/theme/src/Header.tsx +47 -0
  352. package/template/sdk-kit/templates/theme/src/Views.tsx +36 -0
  353. package/template/sdk-kit/templates/theme/src/blocks.tsx +93 -0
  354. package/template/sdk-kit/templates/theme/src/index.tsx +30 -0
  355. package/template/sdk-kit/templates/theme/src/site.tsx +59 -0
  356. package/template/sdk-kit/templates/theme/src/styles.css +71 -0
  357. package/template/server/client/routes.ts +252 -0
  358. package/template/server/cms/analyticsStore.ts +180 -0
  359. package/template/server/cms/auth.ts +155 -0
  360. package/template/server/cms/blockCatalog.ts +342 -0
  361. package/template/server/cms/blocks.ts +521 -0
  362. package/template/server/cms/contentMigration.ts +1119 -0
  363. package/template/server/cms/defaults.ts +303 -0
  364. package/template/server/cms/permissions.ts +154 -0
  365. package/template/server/cms/restoreClassicHome.ts +183 -0
  366. package/template/server/cms/revisions.ts +99 -0
  367. package/template/server/cms/scheduler.ts +78 -0
  368. package/template/server/cms/seed.ts +72 -0
  369. package/template/server/cms/seoAnalyzer.ts +213 -0
  370. package/template/server/cms/store.ts +122 -0
  371. package/template/server/cms/themeContent.ts +1386 -0
  372. package/template/server/cms/themePacks/sidibou-branding.json +1 -0
  373. package/template/server/cms/themePacks/sidibou-home.json +1 -0
  374. package/template/server/cms/themePacks/sidibou-menus.json +1 -0
  375. package/template/server/cms/types.ts +432 -0
  376. package/template/server/community/routes.ts +410 -0
  377. package/template/server/db/client.ts +43 -0
  378. package/template/server/db/repository.ts +389 -0
  379. package/template/server/db/schema.ts +726 -0
  380. package/template/server/demo/cmsReset.ts +196 -0
  381. package/template/server/developers/sdk.ts +85 -0
  382. package/template/server/geoip.ts +15 -0
  383. package/template/server/http.ts +53 -0
  384. package/template/server/mail.ts +216 -0
  385. package/template/server/marketplace/catalog.ts +74 -0
  386. package/template/server/marketplace/packageValidate.ts +475 -0
  387. package/template/server/marketplace/payments.ts +193 -0
  388. package/template/server/marketplace/routes.ts +850 -0
  389. package/template/server/marketplace/signing.ts +116 -0
  390. package/template/server/marketplace/syncOfficial.ts +282 -0
  391. package/template/server/media/images.ts +63 -0
  392. package/template/server/middleware/adminAuth.ts +134 -0
  393. package/template/server/modules/legacy.ts +44 -0
  394. package/template/server/modules/manager.ts +1122 -0
  395. package/template/server/modules/marketplace.ts +178 -0
  396. package/template/server/modules/outbound.ts +178 -0
  397. package/template/server/modules/purchases.ts +149 -0
  398. package/template/server/modules/records.ts +168 -0
  399. package/template/server/modules/themeHtml.ts +32 -0
  400. package/template/server/modules/trust.ts +16 -0
  401. package/template/server/modules/types.ts +147 -0
  402. package/template/server/platform/auth.ts +223 -0
  403. package/template/server/platform/clientWelcome.ts +168 -0
  404. package/template/server/platform/clients.ts +139 -0
  405. package/template/server/platform/dailyImprove.ts +356 -0
  406. package/template/server/platform/flouci.ts +126 -0
  407. package/template/server/platform/host.ts +69 -0
  408. package/template/server/platform/hostingProviders.ts +143 -0
  409. package/template/server/platform/licenseOrders.ts +291 -0
  410. package/template/server/platform/money.ts +42 -0
  411. package/template/server/platform/passwords.ts +41 -0
  412. package/template/server/platform/region.ts +86 -0
  413. package/template/server/platform/routes.ts +225 -0
  414. package/template/server/platform/seed.ts +82 -0
  415. package/template/server/platform/settings.ts +72 -0
  416. package/template/server/routes/adminAccount.ts +403 -0
  417. package/template/server/routes/adminCms.ts +808 -0
  418. package/template/server/routes/adminSystem.ts +243 -0
  419. package/template/server/routes/adminUsers.ts +449 -0
  420. package/template/server/routes/googleOAuth.ts +327 -0
  421. package/template/server/routes/modules.ts +323 -0
  422. package/template/server/routes/publicCms.ts +313 -0
  423. package/template/server/routes/seoPublic.ts +158 -0
  424. package/template/server/security/accounts.ts +121 -0
  425. package/template/server/security/audit.ts +143 -0
  426. package/template/server/security/authTokens.ts +74 -0
  427. package/template/server/security/ip.ts +68 -0
  428. package/template/server/security/rateLimit.ts +85 -0
  429. package/template/server/security/retention.ts +46 -0
  430. package/template/server/security/secrets.ts +44 -0
  431. package/template/server/security/sessions.ts +140 -0
  432. package/template/server/security/totp.ts +101 -0
  433. package/template/server/superadmin/analytics.ts +109 -0
  434. package/template/server/superadmin/auth.ts +92 -0
  435. package/template/server/superadmin/backlinksCatalog.ts +210 -0
  436. package/template/server/superadmin/frSeoContent.ts +165 -0
  437. package/template/server/superadmin/landingSeo.ts +675 -0
  438. package/template/server/superadmin/ops.ts +238 -0
  439. package/template/server/superadmin/routes.ts +1095 -0
  440. package/template/server/superadmin/tnSeoContent.ts +130 -0
  441. package/template/server.ts +522 -0
  442. package/template/src/App.tsx +263 -0
  443. package/template/src/admin/AdminApp.tsx +162 -0
  444. package/template/src/admin/account/AuthLayout.tsx +57 -0
  445. package/template/src/admin/account/SecurityForms.tsx +276 -0
  446. package/template/src/admin/admin.css +127 -0
  447. package/template/src/admin/context/AuthContext.tsx +147 -0
  448. package/template/src/admin/context/SiteContext.tsx +50 -0
  449. package/template/src/admin/editor/BlockLibrary.tsx +173 -0
  450. package/template/src/admin/editor/BlocksPanel.tsx +396 -0
  451. package/template/src/admin/editor/EditorBits.tsx +106 -0
  452. package/template/src/admin/editor/PreviewFrame.tsx +145 -0
  453. package/template/src/admin/editor/RichTextEditor.tsx +276 -0
  454. package/template/src/admin/editor/SeoPanel.tsx +349 -0
  455. package/template/src/admin/editor/StyleEditor.tsx +114 -0
  456. package/template/src/admin/editor/blockSchemas.ts +699 -0
  457. package/template/src/admin/editor/fields.tsx +486 -0
  458. package/template/src/admin/layout/AdminShell.tsx +418 -0
  459. package/template/src/admin/layout/nav.ts +95 -0
  460. package/template/src/admin/lib/api.ts +408 -0
  461. package/template/src/admin/lib/audit.ts +141 -0
  462. package/template/src/admin/lib/format.ts +84 -0
  463. package/template/src/admin/lib/hooks.ts +125 -0
  464. package/template/src/admin/main.tsx +16 -0
  465. package/template/src/admin/modules/InstallDialog.tsx +160 -0
  466. package/template/src/admin/modules/SettingsForm.tsx +145 -0
  467. package/template/src/admin/modules/kit.tsx +127 -0
  468. package/template/src/admin/modules/labels.tsx +226 -0
  469. package/template/src/admin/pages/AccountSetupPage.tsx +62 -0
  470. package/template/src/admin/pages/AnalyticsPage.tsx +206 -0
  471. package/template/src/admin/pages/BrandingPage.tsx +154 -0
  472. package/template/src/admin/pages/DashboardPage.tsx +239 -0
  473. package/template/src/admin/pages/EngagementPage.tsx +168 -0
  474. package/template/src/admin/pages/FormsPage.tsx +435 -0
  475. package/template/src/admin/pages/GeneralPage.tsx +120 -0
  476. package/template/src/admin/pages/LoginPage.tsx +249 -0
  477. package/template/src/admin/pages/MaintenancePage.tsx +125 -0
  478. package/template/src/admin/pages/MarketplacePage.tsx +426 -0
  479. package/template/src/admin/pages/MediaPage.tsx +251 -0
  480. package/template/src/admin/pages/MenusPage.tsx +446 -0
  481. package/template/src/admin/pages/ModuleAdminPage.tsx +113 -0
  482. package/template/src/admin/pages/ModuleDetailPage.tsx +293 -0
  483. package/template/src/admin/pages/ModulesPage.tsx +135 -0
  484. package/template/src/admin/pages/NewsletterPage.tsx +193 -0
  485. package/template/src/admin/pages/PageEditorPage.tsx +322 -0
  486. package/template/src/admin/pages/PagesListPage.tsx +277 -0
  487. package/template/src/admin/pages/PostEditorPage.tsx +292 -0
  488. package/template/src/admin/pages/PostsListPage.tsx +187 -0
  489. package/template/src/admin/pages/ProfilePage.tsx +489 -0
  490. package/template/src/admin/pages/ResetPasswordPage.tsx +110 -0
  491. package/template/src/admin/pages/SeoPage.tsx +670 -0
  492. package/template/src/admin/pages/SettingsPage.tsx +189 -0
  493. package/template/src/admin/pages/ThemeCustomizePage.tsx +170 -0
  494. package/template/src/admin/pages/ThemesPage.tsx +292 -0
  495. package/template/src/admin/pages/UsersPage.tsx +57 -0
  496. package/template/src/admin/pages/WidgetsPage.tsx +200 -0
  497. package/template/src/admin/seo/GoogleSetup.tsx +529 -0
  498. package/template/src/admin/settings/EmailTab.tsx +293 -0
  499. package/template/src/admin/settings/OtherTabs.tsx +270 -0
  500. package/template/src/admin/settings/SecurityTab.tsx +172 -0
  501. package/template/src/admin/settings/controls.tsx +133 -0
  502. package/template/src/admin/ui/Pagination.tsx +170 -0
  503. package/template/src/admin/ui/SaveBar.tsx +77 -0
  504. package/template/src/admin/ui/TagInput.tsx +54 -0
  505. package/template/src/admin/ui/charts.tsx +108 -0
  506. package/template/src/admin/ui/media.tsx +265 -0
  507. package/template/src/admin/ui/overlays.tsx +239 -0
  508. package/template/src/admin/ui/primitives.tsx +544 -0
  509. package/template/src/admin/users/AuditTab.tsx +104 -0
  510. package/template/src/admin/users/RolesTab.tsx +368 -0
  511. package/template/src/admin/users/UsersTab.tsx +723 -0
  512. package/template/src/admin/users/shared.tsx +278 -0
  513. package/template/src/cms/icons.ts +7 -0
  514. package/template/src/components/CookieBanner.tsx +166 -0
  515. package/template/src/components/MaintenanceGate.tsx +66 -0
  516. package/template/src/components/SiteAnalytics.tsx +64 -0
  517. package/template/src/components/SnapifyLogo.tsx +44 -0
  518. package/template/src/components/cms/BlockRenderer.tsx +154 -0
  519. package/template/src/components/cms/WidgetArea.tsx +193 -0
  520. package/template/src/context/SiteConfigContext.tsx +130 -0
  521. package/template/src/demoBase.ts +23 -0
  522. package/template/src/index.css +90 -0
  523. package/template/src/main.tsx +16 -0
  524. package/template/src/modules/ModuleHost.tsx +197 -0
  525. package/template/src/preview.ts +63 -0
  526. package/template/src/runtime.ts +38 -0
  527. package/template/src/themes/ThemeHost.tsx +168 -0
  528. package/template/src/themes/fallback.tsx +71 -0
  529. package/template/src/types.ts +94 -0
  530. package/template/src/views/BlogPostView.tsx +68 -0
  531. package/template/src/views/CmsPageView.tsx +34 -0
  532. package/template/start-pm2.sh +3 -0
  533. package/template/superadmin.html +20 -0
  534. package/template/themes/ai-canvas/screenshot.jpg +0 -0
  535. package/template/themes/ai-canvas/snapify.theme.json +96 -0
  536. package/template/themes/ai-canvas/src/CeeSimulator.tsx +247 -0
  537. package/template/themes/ai-canvas/src/Footer.tsx +190 -0
  538. package/template/themes/ai-canvas/src/Header.tsx +324 -0
  539. package/template/themes/ai-canvas/src/Layout.tsx +56 -0
  540. package/template/themes/ai-canvas/src/NotFound.tsx +58 -0
  541. package/template/themes/ai-canvas/src/Post.tsx +26 -0
  542. package/template/themes/ai-canvas/src/SnapifyLogo.tsx +44 -0
  543. package/template/themes/ai-canvas/src/blocks/CollectionBlocks.tsx +711 -0
  544. package/template/themes/ai-canvas/src/blocks/ContentBlocks.tsx +318 -0
  545. package/template/themes/ai-canvas/src/blocks/ConversionBlocks.tsx +559 -0
  546. package/template/themes/ai-canvas/src/blocks/HeaderBlocks.tsx +341 -0
  547. package/template/themes/ai-canvas/src/index.tsx +54 -0
  548. package/template/themes/ai-canvas/src/kit.tsx +398 -0
  549. package/template/themes/ai-canvas/src/site.ts +29 -0
  550. package/template/themes/ai-canvas/src/styles.css +317 -0
  551. package/template/themes/dogga/screenshot.jpg +0 -0
  552. package/template/themes/dogga/snapify.theme.json +55 -0
  553. package/template/themes/dogga/src/Brand.tsx +41 -0
  554. package/template/themes/dogga/src/Footer.tsx +202 -0
  555. package/template/themes/dogga/src/Header.tsx +239 -0
  556. package/template/themes/dogga/src/Layout.tsx +43 -0
  557. package/template/themes/dogga/src/Views.tsx +71 -0
  558. package/template/themes/dogga/src/blocks/CollectionBlocks.tsx +674 -0
  559. package/template/themes/dogga/src/blocks/ContentBlocks.tsx +307 -0
  560. package/template/themes/dogga/src/blocks/ConversionBlocks.tsx +571 -0
  561. package/template/themes/dogga/src/blocks/HeaderBlocks.tsx +289 -0
  562. package/template/themes/dogga/src/index.tsx +53 -0
  563. package/template/themes/dogga/src/kit.tsx +362 -0
  564. package/template/themes/dogga/src/site.ts +56 -0
  565. package/template/themes/dogga/src/styles.css +396 -0
  566. package/template/themes/mahdia/screenshot.jpg +0 -0
  567. package/template/themes/mahdia/snapify.theme.json +65 -0
  568. package/template/themes/mahdia/src/Brand.tsx +39 -0
  569. package/template/themes/mahdia/src/Footer.tsx +210 -0
  570. package/template/themes/mahdia/src/Header.tsx +291 -0
  571. package/template/themes/mahdia/src/Layout.tsx +44 -0
  572. package/template/themes/mahdia/src/Views.tsx +79 -0
  573. package/template/themes/mahdia/src/blocks/CollectionBlocks.tsx +674 -0
  574. package/template/themes/mahdia/src/blocks/ContentBlocks.tsx +307 -0
  575. package/template/themes/mahdia/src/blocks/ConversionBlocks.tsx +571 -0
  576. package/template/themes/mahdia/src/blocks/HeaderBlocks.tsx +289 -0
  577. package/template/themes/mahdia/src/index.tsx +53 -0
  578. package/template/themes/mahdia/src/kit.tsx +362 -0
  579. package/template/themes/mahdia/src/site.ts +75 -0
  580. package/template/themes/mahdia/src/styles.css +446 -0
  581. package/template/themes/preveris/screenshot.jpg +0 -0
  582. package/template/themes/preveris/snapify.theme.json +126 -0
  583. package/template/themes/preveris/src/Brand.tsx +44 -0
  584. package/template/themes/preveris/src/Footer.tsx +202 -0
  585. package/template/themes/preveris/src/Header.tsx +239 -0
  586. package/template/themes/preveris/src/Layout.tsx +42 -0
  587. package/template/themes/preveris/src/Views.tsx +71 -0
  588. package/template/themes/preveris/src/blocks/CollectionBlocks.tsx +678 -0
  589. package/template/themes/preveris/src/blocks/ContentBlocks.tsx +307 -0
  590. package/template/themes/preveris/src/blocks/ConversionBlocks.tsx +571 -0
  591. package/template/themes/preveris/src/blocks/HeaderBlocks.tsx +299 -0
  592. package/template/themes/preveris/src/index.tsx +53 -0
  593. package/template/themes/preveris/src/kit.tsx +365 -0
  594. package/template/themes/preveris/src/site.ts +79 -0
  595. package/template/themes/preveris/src/styles.css +446 -0
  596. package/template/themes/sidibou/screenshot.jpg +0 -0
  597. package/template/themes/sidibou/snapify.theme.json +21 -0
  598. package/template/themes/sidibou/src/CeeSimulator.tsx +247 -0
  599. package/template/themes/sidibou/src/Footer.tsx +203 -0
  600. package/template/themes/sidibou/src/Header.tsx +324 -0
  601. package/template/themes/sidibou/src/Layout.tsx +53 -0
  602. package/template/themes/sidibou/src/NotFound.tsx +58 -0
  603. package/template/themes/sidibou/src/Post.tsx +26 -0
  604. package/template/themes/sidibou/src/SnapifyLogo.tsx +44 -0
  605. package/template/themes/sidibou/src/blocks/CollectionBlocks.tsx +711 -0
  606. package/template/themes/sidibou/src/blocks/ContentBlocks.tsx +318 -0
  607. package/template/themes/sidibou/src/blocks/ConversionBlocks.tsx +559 -0
  608. package/template/themes/sidibou/src/blocks/HeaderBlocks.tsx +341 -0
  609. package/template/themes/sidibou/src/index.tsx +54 -0
  610. package/template/themes/sidibou/src/kit.tsx +398 -0
  611. package/template/themes/sidibou/src/site.ts +28 -0
  612. package/template/themes/sidibou/src/styles.css +317 -0
  613. package/template/tsconfig.json +33 -0
  614. package/template/vite.config.ts +42 -0
@@ -0,0 +1,714 @@
1
+ /** Valide et complète un ThemeSpec pour qu’il soit 100 % applicable Snapify. */
2
+ import {
3
+ ALLOWED_FONTS,
4
+ ALLOWED_HOME_TYPES,
5
+ type BrandColors,
6
+ type HomeBlock,
7
+ type ThemeSettingsSpec,
8
+ type ThemeSpec,
9
+ } from '../types';
10
+
11
+ const HEX = /^#[0-9A-Fa-f]{6}$/;
12
+
13
+ function hex(v: unknown, fallback: string): string {
14
+ const s = String(v || '').trim();
15
+ return HEX.test(s) ? s.toUpperCase() : fallback;
16
+ }
17
+
18
+ function font(v: unknown, fallback: string): string {
19
+ const s = String(v || '').trim();
20
+ return (ALLOWED_FONTS as readonly string[]).includes(s) ? s : fallback;
21
+ }
22
+
23
+ function bool(v: unknown, fallback: boolean): boolean {
24
+ return typeof v === 'boolean' ? v : fallback;
25
+ }
26
+
27
+ function str(v: unknown, max = 200): string {
28
+ return typeof v === 'string' ? v.trim().slice(0, max) : '';
29
+ }
30
+
31
+ function uid(prefix: string): string {
32
+ return `${prefix}_${Math.random().toString(36).slice(2, 9)}`;
33
+ }
34
+
35
+ function button(raw: unknown, fallbackLabel: string, fallbackLink = '/contact/') {
36
+ const o = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
37
+ const variant = ['primary', 'secondary', 'dark', 'light', 'glass', 'link'].includes(String(o.variant))
38
+ ? String(o.variant)
39
+ : 'primary';
40
+ const icon = ['arrow', 'arrow-up-right', 'phone', 'mail', 'download', 'none'].includes(String(o.icon))
41
+ ? String(o.icon)
42
+ : 'arrow';
43
+ return {
44
+ id: str(o.id, 40) || uid('btn'),
45
+ label: str(o.label, 60) || fallbackLabel,
46
+ link: str(o.link, 200) || fallbackLink,
47
+ variant,
48
+ icon,
49
+ newTab: !!o.newTab,
50
+ };
51
+ }
52
+
53
+ function style(raw: unknown): Record<string, unknown> {
54
+ const o = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
55
+ const bg = ['none', 'white', 'surface', 'soft', 'navy', 'brand'].includes(String(o.background))
56
+ ? String(o.background)
57
+ : 'white';
58
+ const pad = ['none', 'sm', 'md', 'lg', 'xl'].includes(String(o.paddingTop)) ? String(o.paddingTop) : 'lg';
59
+ const padB = ['none', 'sm', 'md', 'lg', 'xl'].includes(String(o.paddingBottom)) ? String(o.paddingBottom) : 'lg';
60
+ const width = ['narrow', 'default', 'wide', 'full'].includes(String(o.width)) ? String(o.width) : 'default';
61
+ const animation = ['none', 'fade-up', 'fade', 'zoom'].includes(String(o.animation)) ? String(o.animation) : 'fade-up';
62
+ return {
63
+ background: bg,
64
+ decor: 'none',
65
+ paddingTop: pad,
66
+ paddingBottom: padB,
67
+ width,
68
+ animation,
69
+ };
70
+ }
71
+
72
+ function heading(o: Record<string, unknown>) {
73
+ return {
74
+ eyebrow: str(o.eyebrow, 80) || undefined,
75
+ title: str(o.title, 120) || 'Section',
76
+ highlight: str(o.highlight, 80) || undefined,
77
+ intro: str(o.intro, 400) || undefined,
78
+ align: o.align === 'center' ? 'center' : 'left',
79
+ };
80
+ }
81
+
82
+ function normalizeBlock(raw: unknown, index: number): HomeBlock | null {
83
+ if (!raw || typeof raw !== 'object') return null;
84
+ const b = raw as Record<string, unknown>;
85
+ const type = String(b.type || '');
86
+ if (!(ALLOWED_HOME_TYPES as readonly string[]).includes(type)) return null;
87
+ const dataIn = b.data && typeof b.data === 'object' ? (b.data as Record<string, unknown>) : {};
88
+ const st = style(b.style);
89
+
90
+ if (type === 'hero') {
91
+ let buttons = Array.isArray(dataIn.buttons)
92
+ ? dataIn.buttons.slice(0, 3).map((x, i) =>
93
+ button(
94
+ x,
95
+ i === 0 ? 'Nous contacter' : 'En savoir plus',
96
+ i === 0 ? '/contact/' : '/services/'
97
+ )
98
+ )
99
+ : [];
100
+ if (!buttons.length) {
101
+ buttons = [
102
+ button({ variant: 'primary', icon: 'arrow', label: 'Nous contacter', link: '/contact/' }, 'Nous contacter'),
103
+ button({ variant: 'secondary', icon: 'arrow-up-right', label: 'Nos services', link: '/services/' }, 'Nos services', '/services/'),
104
+ ];
105
+ } else if (buttons.length === 1) {
106
+ buttons.push(button({ variant: 'secondary', icon: 'arrow-up-right', label: 'En savoir plus', link: '/a-propos/' }, 'En savoir plus', '/a-propos/'));
107
+ }
108
+ // Premier = primary, second = secondary si non précisé
109
+ if (buttons[0]) buttons[0] = { ...buttons[0], variant: buttons[0].variant === 'secondary' ? 'primary' : buttons[0].variant };
110
+ if (buttons[1] && buttons[1].variant === 'primary') buttons[1] = { ...buttons[1], variant: 'secondary' };
111
+
112
+ let trustItems = Array.isArray(dataIn.trustItems)
113
+ ? dataIn.trustItems.map((t) => str(t, 80)).filter(Boolean).slice(0, 4)
114
+ : [];
115
+ if (trustItems.length < 2) {
116
+ trustItems = ['Réponse sous 24 h', 'Équipe locale', 'Devis clair'].slice(0, 3);
117
+ }
118
+
119
+ return {
120
+ type,
121
+ data: {
122
+ layout: dataIn.layout === 'centered' ? 'centered' : 'split',
123
+ badgeText: str(dataIn.badgeText, 60),
124
+ title: str(dataIn.title, 120) || 'Bienvenue',
125
+ highlight: str(dataIn.highlight, 80),
126
+ subtitle: str(dataIn.subtitle, 300),
127
+ rotatingWords: Array.isArray(dataIn.rotatingWords)
128
+ ? dataIn.rotatingWords.map((w) => str(w, 40)).filter(Boolean).slice(0, 4)
129
+ : [],
130
+ buttons,
131
+ liveText: str(dataIn.liveText, 80),
132
+ trustItems,
133
+ media: 'image',
134
+ mainImage: str(dataIn.mainImage, 500),
135
+ mainImageAlt: str(dataIn.mainImageAlt, 120),
136
+ parallax: bool(dataIn.parallax, false),
137
+ marquee: [],
138
+ },
139
+ style: { ...st, background: st.background === 'white' ? 'soft' : st.background, paddingTop: 'xl', paddingBottom: 'xl' },
140
+ };
141
+ }
142
+
143
+ if (type === 'stats') {
144
+ const items = (Array.isArray(dataIn.items) ? dataIn.items : []).slice(0, 4).map((it, i) => {
145
+ const o = it && typeof it === 'object' ? (it as Record<string, unknown>) : {};
146
+ return {
147
+ id: str(o.id, 40) || uid('st'),
148
+ value: str(o.value, 20) || String((i + 1) * 10),
149
+ prefix: str(o.prefix, 8) || undefined,
150
+ suffix: str(o.suffix, 8) || undefined,
151
+ label: str(o.label, 60) || 'Indicateur',
152
+ hint: str(o.hint, 80) || undefined,
153
+ };
154
+ });
155
+ while (items.length < 3) {
156
+ items.push({ id: uid('st'), value: String(items.length * 25 + 50), label: 'Résultat', prefix: undefined, suffix: '+', hint: undefined });
157
+ }
158
+ return {
159
+ type,
160
+ data: { ...heading(dataIn), style: 'cards', countUp: true, items },
161
+ style: st,
162
+ };
163
+ }
164
+
165
+ if (type === 'cards') {
166
+ const icons = ['sparkles', 'shield', 'heart', 'star', 'check', 'phone'];
167
+ const items = (Array.isArray(dataIn.items) ? dataIn.items : []).slice(0, 6).map((it, i) => {
168
+ const o = it && typeof it === 'object' ? (it as Record<string, unknown>) : {};
169
+ return {
170
+ id: str(o.id, 40) || uid('cd'),
171
+ icon: str(o.icon, 40) || icons[i % icons.length]!,
172
+ title: str(o.title, 80) || 'Service',
173
+ text: str(o.text, 240) || 'Une offre claire, adaptée à vos besoins.',
174
+ image: str(o.image, 500) || undefined,
175
+ tags: [],
176
+ linkLabel: str(o.linkLabel, 40) || 'En savoir plus',
177
+ link: str(o.link, 200) || '/services/',
178
+ };
179
+ });
180
+ while (items.length < 3) {
181
+ const i = items.length;
182
+ items.push({
183
+ id: uid('cd'),
184
+ icon: icons[i % icons.length]!,
185
+ title: 'Offre',
186
+ text: 'Description à personnaliser.',
187
+ image: undefined,
188
+ tags: [],
189
+ linkLabel: 'En savoir plus',
190
+ link: '/services/',
191
+ });
192
+ }
193
+ const hasAnyImage = items.some((it) => !!it.image);
194
+ return {
195
+ type,
196
+ data: {
197
+ ...heading(dataIn),
198
+ style: hasAnyImage ? 'image' : 'card',
199
+ columns: items.length >= 4 ? 4 : 3,
200
+ items,
201
+ },
202
+ style: st,
203
+ };
204
+ }
205
+
206
+ if (type === 'steps') {
207
+ const items = (Array.isArray(dataIn.items) ? dataIn.items : []).slice(0, 5).map((it, i) => {
208
+ const o = it && typeof it === 'object' ? (it as Record<string, unknown>) : {};
209
+ return {
210
+ id: str(o.id, 40) || uid('sp'),
211
+ num: str(o.num, 4) || String(i + 1).padStart(2, '0'),
212
+ title: str(o.title, 80) || `Étape ${i + 1}`,
213
+ text: str(o.text, 240) || '',
214
+ deliverable: str(o.deliverable, 120) || undefined,
215
+ };
216
+ });
217
+ while (items.length < 3) {
218
+ const i = items.length;
219
+ items.push({ id: uid('sp'), num: String(i + 1).padStart(2, '0'), title: `Étape ${i + 1}`, text: '', deliverable: undefined });
220
+ }
221
+ return {
222
+ type,
223
+ data: {
224
+ ...heading(dataIn),
225
+ style: 'timeline',
226
+ deliverableLabel: str(dataIn.deliverableLabel, 40) || 'Livrable',
227
+ items,
228
+ buttons: [button(Array.isArray(dataIn.buttons) ? dataIn.buttons[0] : {}, 'Démarrer')],
229
+ },
230
+ style: st,
231
+ };
232
+ }
233
+
234
+ if (type === 'faq') {
235
+ const items = (Array.isArray(dataIn.items) ? dataIn.items : []).slice(0, 8).map((it) => {
236
+ const o = it && typeof it === 'object' ? (it as Record<string, unknown>) : {};
237
+ return {
238
+ id: str(o.id, 40) || uid('fq'),
239
+ question: str(o.question || o.title, 160) || 'Question ?',
240
+ answer: str(o.answer || o.text, 600) || 'Réponse à compléter.',
241
+ };
242
+ });
243
+ while (items.length < 3) {
244
+ items.push({ id: uid('fq'), question: 'Une question fréquente ?', answer: 'Nous vous répondons rapidement.' });
245
+ }
246
+ return {
247
+ type,
248
+ data: {
249
+ ...heading(dataIn),
250
+ layout: dataIn.layout === 'stacked' ? 'stacked' : 'split',
251
+ showContactCard: bool(dataIn.showContactCard, true),
252
+ contactLabel: str(dataIn.contactLabel, 60) || 'Une autre question ?',
253
+ items,
254
+ },
255
+ style: st,
256
+ };
257
+ }
258
+
259
+ if (type === 'testimonials') {
260
+ const items = (Array.isArray(dataIn.items) ? dataIn.items : []).slice(0, 6).map((it) => {
261
+ const o = it && typeof it === 'object' ? (it as Record<string, unknown>) : {};
262
+ return {
263
+ id: str(o.id, 40) || uid('tm'),
264
+ quote: str(o.quote || o.text, 400) || 'Un retour client positif.',
265
+ author: str(o.author || o.name, 80) || 'Client',
266
+ role: str(o.role, 80) || '',
267
+ image: '',
268
+ };
269
+ });
270
+ while (items.length < 2) {
271
+ items.push({ id: uid('tm'), quote: 'Excellent accompagnement.', author: 'Client satisfait', role: '', image: '' });
272
+ }
273
+ return {
274
+ type,
275
+ data: { ...heading(dataIn), items },
276
+ style: st,
277
+ };
278
+ }
279
+
280
+ if (type === 'cta') {
281
+ let buttons = Array.isArray(dataIn.buttons)
282
+ ? dataIn.buttons.slice(0, 2).map((x, i) =>
283
+ button(
284
+ x,
285
+ i === 0 ? 'Parler à un expert' : 'Voir nos services',
286
+ i === 0 ? '/contact/' : '/services/'
287
+ )
288
+ )
289
+ : [
290
+ button({ variant: 'primary', icon: 'arrow', label: 'Parler à un expert', link: '/contact/' }, 'Parler à un expert'),
291
+ button({ variant: 'light', icon: 'arrow-up-right', label: 'Nos services', link: '/services/' }, 'Nos services', '/services/'),
292
+ ];
293
+ if (buttons.length === 1) {
294
+ buttons.push(button({ variant: 'light', icon: 'arrow-up-right', label: 'Nos services', link: '/services/' }, 'Nos services', '/services/'));
295
+ }
296
+ const ctaVariant = dataIn.variant === 'light' ? 'light' : dataIn.variant === 'gradient' ? 'gradient' : 'dark';
297
+ // Sur fond sombre : primaire + light (blanc) pour contraste
298
+ if (buttons[0]) buttons[0] = { ...buttons[0], variant: 'primary', icon: buttons[0].icon === 'none' ? 'arrow' : buttons[0].icon };
299
+ if (buttons[1]) {
300
+ buttons[1] = {
301
+ ...buttons[1],
302
+ variant: ctaVariant === 'light' ? 'secondary' : 'light',
303
+ icon: buttons[1].icon === 'none' ? 'arrow-up-right' : buttons[1].icon,
304
+ };
305
+ }
306
+ return {
307
+ type,
308
+ data: {
309
+ variant: ctaVariant,
310
+ layout: dataIn.layout === 'centered' ? 'centered' : 'split',
311
+ eyebrow: str(dataIn.eyebrow, 60) || 'Prochaine étape',
312
+ title: str(dataIn.title, 120) || 'Prêts à démarrer ?',
313
+ highlight: str(dataIn.highlight, 80),
314
+ text: str(dataIn.text, 300) || 'Parlez-nous de votre projet — réponse sous 24 h.',
315
+ buttons,
316
+ image: str(dataIn.image, 500),
317
+ imageAlt: str(dataIn.imageAlt, 120),
318
+ },
319
+ style: { ...st, background: 'navy', paddingTop: 'xl', paddingBottom: 'xl' },
320
+ };
321
+ }
322
+
323
+ if (type === 'textMedia') {
324
+ let bullets = Array.isArray(dataIn.bullets) ? dataIn.bullets.map((x) => str(x, 120)).filter(Boolean).slice(0, 6) : [];
325
+ if (bullets.length < 3) {
326
+ bullets = [
327
+ 'Une approche claire et personnalisée',
328
+ 'Des interlocuteurs joignables et réactifs',
329
+ 'Un suivi transparent à chaque étape',
330
+ ];
331
+ }
332
+ let tmButtons = Array.isArray(dataIn.buttons)
333
+ ? dataIn.buttons.slice(0, 2).map((x, i) => button(x, i === 0 ? 'À propos' : 'Nous contacter', i === 0 ? '/a-propos/' : '/contact/'))
334
+ : [button({ variant: 'primary', icon: 'arrow', label: 'À propos', link: '/a-propos/' }, 'À propos', '/a-propos/')];
335
+ if (tmButtons.length === 1) {
336
+ tmButtons.push(button({ variant: 'secondary', icon: 'arrow-up-right', label: 'Nous contacter', link: '/contact/' }, 'Nous contacter', '/contact/'));
337
+ }
338
+ return {
339
+ type,
340
+ data: {
341
+ ...heading(dataIn),
342
+ html: str(dataIn.html, 4000) || `<p>${str(dataIn.intro || dataIn.text, 400) || 'Présentation de votre activité.'}</p>`,
343
+ bullets,
344
+ buttons: tmButtons,
345
+ mediaType: 'image',
346
+ image: str(dataIn.image, 500),
347
+ imageAlt: str(dataIn.imageAlt, 120),
348
+ videoSource: 'file',
349
+ mediaPosition: dataIn.mediaPosition === 'left' ? 'left' : 'right',
350
+ mediaStyle: 'rounded',
351
+ ratio: 'landscape',
352
+ autoplay: false,
353
+ },
354
+ style: st,
355
+ };
356
+ }
357
+
358
+ if (type === 'checklist') {
359
+ const entries = (Array.isArray(dataIn.items) ? dataIn.items : Array.isArray(dataIn.entries) ? dataIn.entries : [])
360
+ .slice(0, 8)
361
+ .map((it) => {
362
+ const o = typeof it === 'string' ? { title: it } : it && typeof it === 'object' ? (it as Record<string, unknown>) : {};
363
+ return {
364
+ id: str(o.id, 40) || uid('ck'),
365
+ title: str(o.title || o.text, 120) || 'Point clé',
366
+ text: str(o.text, 200) || undefined,
367
+ };
368
+ });
369
+ while (entries.length < 3) entries.push({ id: uid('ck'), title: 'Avantage clé', text: undefined });
370
+ return {
371
+ type,
372
+ data: {
373
+ ...heading(dataIn),
374
+ cards: [
375
+ {
376
+ id: uid('card'),
377
+ icon: 'check',
378
+ tag: str(dataIn.tag, 40) || 'Inclus',
379
+ title: str(dataIn.cardTitle, 80) || str(dataIn.title, 80) || 'Ce que vous obtenez',
380
+ entries,
381
+ },
382
+ ],
383
+ },
384
+ style: st,
385
+ };
386
+ }
387
+
388
+ if (type === 'contactForm') {
389
+ return {
390
+ type,
391
+ data: {
392
+ formId: str(dataIn.formId, 40) || 'contact',
393
+ title: str(dataIn.title, 120) || 'Contactez-nous',
394
+ intro: str(dataIn.intro, 300) || 'Réponse sous 24 h ouvrées.',
395
+ phoneLabel: str(dataIn.phoneLabel, 40) || 'Téléphone',
396
+ phoneHint: str(dataIn.phoneHint, 80) || '',
397
+ listTitle: str(dataIn.listTitle, 80) || 'Pourquoi nous écrire',
398
+ listItems: Array.isArray(dataIn.listItems)
399
+ ? dataIn.listItems.map((x) => str(x, 120)).filter(Boolean).slice(0, 5)
400
+ : ['Devis sous 48 h', 'Écoute personnalisée', 'Suivi clair'],
401
+ submitLabel: str(dataIn.submitLabel, 40) || 'Envoyer',
402
+ },
403
+ style: st,
404
+ };
405
+ }
406
+
407
+ if (type === 'showcase') {
408
+ const items = (Array.isArray(dataIn.items) ? dataIn.items : []).slice(0, 4).map((it) => {
409
+ const o = it && typeof it === 'object' ? (it as Record<string, unknown>) : {};
410
+ return {
411
+ id: str(o.id, 40) || uid('sh'),
412
+ icon: str(o.icon, 40) || 'star',
413
+ title: str(o.title, 80) || 'Projet',
414
+ text: str(o.text, 240) || '',
415
+ image: '',
416
+ chips: [],
417
+ buttonLabel: str(o.buttonLabel, 40) || undefined,
418
+ link: str(o.link, 200) || undefined,
419
+ };
420
+ });
421
+ while (items.length < 2) {
422
+ items.push({ id: uid('sh'), icon: 'star', title: 'Réalisation', text: '', image: '', chips: [], buttonLabel: undefined, link: undefined });
423
+ }
424
+ return {
425
+ type,
426
+ data: { ...heading(dataIn), interval: 6, items },
427
+ style: st,
428
+ };
429
+ }
430
+
431
+ if (type === 'spacer') {
432
+ const height = Math.min(160, Math.max(24, Number(dataIn.height) || 64));
433
+ return { type, data: { height, divider: !!dataIn.divider }, style: { ...st, paddingTop: 'none', paddingBottom: 'none' } };
434
+ }
435
+
436
+ // pageHeader / gallery fallbacks kept minimal
437
+ if (type === 'pageHeader') {
438
+ return {
439
+ type,
440
+ data: {
441
+ breadcrumb: false,
442
+ title: str(dataIn.title, 120) || 'Page',
443
+ highlight: str(dataIn.highlight, 80),
444
+ intro: str(dataIn.intro, 300),
445
+ buttons: [],
446
+ },
447
+ style: st,
448
+ };
449
+ }
450
+
451
+ void index;
452
+ return null;
453
+ }
454
+
455
+ function defaultColors(): BrandColors {
456
+ return {
457
+ primary: '#0D9488',
458
+ primaryHover: '#0F766E',
459
+ accent: '#F59E0B',
460
+ navy: '#0F172A',
461
+ background: '#F8FAFC',
462
+ text: '#0F172A',
463
+ muted: '#64748B',
464
+ border: '#E2E8F0',
465
+ };
466
+ }
467
+
468
+ function defaultSettings(): ThemeSettingsSpec {
469
+ return {
470
+ stickyHeader: true,
471
+ headerPhone: true,
472
+ buttonShape: 'pill',
473
+ cardRadius: 20,
474
+ density: 'comfortable',
475
+ animations: true,
476
+ mobileBar: true,
477
+ backToTop: false,
478
+ };
479
+ }
480
+
481
+ /** Alternance de fonds + ordre marketing classique. */
482
+ function polishStructure(blocks: HomeBlock[]): HomeBlock[] {
483
+ const preferred = ['hero', 'stats', 'cards', 'textMedia', 'steps', 'testimonials', 'faq', 'cta', 'contactForm'];
484
+ const byType = new Map<string, HomeBlock>();
485
+ for (const b of blocks) {
486
+ if (!byType.has(b.type) || b.type === 'spacer') byType.set(b.type, b);
487
+ }
488
+ // garder steps OU showcase (priorité steps)
489
+ if (byType.has('steps') && byType.has('showcase')) byType.delete('showcase');
490
+ // si trop dense : privilégier testimonials, garder faq en plus seulement si < 8
491
+ const ordered: HomeBlock[] = [];
492
+ for (const t of preferred) {
493
+ const b = byType.get(t);
494
+ if (b) ordered.push(b);
495
+ }
496
+ for (const b of blocks) {
497
+ if (!ordered.includes(b) && ordered.length < 8) ordered.push(b);
498
+ }
499
+
500
+ // Cap à 8 blocs : hero + stats + cards + textMedia + steps + social proof + cta (+ contact)
501
+ const capped = ordered.slice(0, 8);
502
+ const bgs = ['soft', 'white', 'surface', 'white', 'soft', 'white', 'navy', 'surface'];
503
+ return capped.map((b, i) => {
504
+ if (b.type === 'cta') {
505
+ return { ...b, style: { ...b.style, background: 'navy', paddingTop: 'xl', paddingBottom: 'xl', animation: 'fade-up' } };
506
+ }
507
+ if (b.type === 'hero') {
508
+ return { ...b, style: { ...b.style, background: 'soft', paddingTop: 'xl', paddingBottom: 'xl', animation: 'fade-up' } };
509
+ }
510
+ if (b.type === 'contactForm') {
511
+ return { ...b, style: { ...b.style, background: 'surface', paddingTop: 'xl', paddingBottom: 'xl', animation: 'fade-up' } };
512
+ }
513
+ return { ...b, style: { ...b.style, background: bgs[i] || 'white', paddingTop: 'lg', paddingBottom: 'lg', animation: 'fade-up' } };
514
+ });
515
+ }
516
+
517
+ export function extractImagePrompts(raw: unknown): { hero?: string; secondary?: string; cards?: string[] } {
518
+ const o = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
519
+ const ip = o.imagePrompts && typeof o.imagePrompts === 'object' ? (o.imagePrompts as Record<string, unknown>) : {};
520
+ return {
521
+ hero: str(ip.hero, 400) || undefined,
522
+ secondary: str(ip.secondary, 400) || undefined,
523
+ cards: Array.isArray(ip.cards) ? ip.cards.map((c) => str(c, 300)).filter(Boolean).slice(0, 3) : undefined,
524
+ };
525
+ }
526
+
527
+ export function normalizeThemeSpec(raw: unknown): ThemeSpec {
528
+ const o = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
529
+ const brandingIn = o.branding && typeof o.branding === 'object' ? (o.branding as Record<string, unknown>) : {};
530
+ const colorsIn = brandingIn.colors && typeof brandingIn.colors === 'object' ? (brandingIn.colors as Record<string, unknown>) : {};
531
+ const fontsIn = brandingIn.fonts && typeof brandingIn.fonts === 'object' ? (brandingIn.fonts as Record<string, unknown>) : {};
532
+ const settingsIn = o.themeSettings && typeof o.themeSettings === 'object' ? (o.themeSettings as Record<string, unknown>) : {};
533
+ const base = defaultColors();
534
+ const settings = defaultSettings();
535
+
536
+ const shape = String(settingsIn.buttonShape);
537
+ const density = String(settingsIn.density);
538
+ const cardRadius = Math.min(28, Math.max(12, Math.round(Number(settingsIn.cardRadius) || settings.cardRadius)));
539
+
540
+ let blocks = (Array.isArray(o.homeBlocks) ? o.homeBlocks : [])
541
+ .map((b, i) => normalizeBlock(b, i))
542
+ .filter((b): b is HomeBlock => !!b);
543
+
544
+ if (!blocks.length || blocks[0]?.type !== 'hero') {
545
+ const hero = normalizeBlock(
546
+ {
547
+ type: 'hero',
548
+ data: {
549
+ title: str(o.siteName, 80) || 'Votre marque',
550
+ subtitle: str(o.tagline, 200),
551
+ layout: 'split',
552
+ badgeText: str(o.industry, 40),
553
+ },
554
+ },
555
+ 0
556
+ )!;
557
+ blocks = [hero, ...blocks.filter((b) => b.type !== 'hero')];
558
+ }
559
+ if (!blocks.some((b) => b.type === 'stats')) {
560
+ const stats = normalizeBlock(
561
+ {
562
+ type: 'stats',
563
+ data: {
564
+ title: 'Ils nous font confiance',
565
+ items: [
566
+ { value: '10', suffix: '+', label: 'Années' },
567
+ { value: '500', suffix: '+', label: 'Clients' },
568
+ { value: '98', suffix: '%', label: 'Satisfaction' },
569
+ { value: '24', suffix: 'h', label: 'Réponse' },
570
+ ],
571
+ },
572
+ },
573
+ 1
574
+ );
575
+ if (stats) blocks.splice(1, 0, stats);
576
+ }
577
+ if (!blocks.some((b) => b.type === 'cards')) {
578
+ const cards = normalizeBlock(
579
+ {
580
+ type: 'cards',
581
+ data: {
582
+ title: 'Nos expertises',
583
+ intro: str(o.tagline, 160),
584
+ items: [
585
+ { icon: 'sparkles', title: 'Conseil', text: 'Un accompagnement clair, adapté à votre contexte.', linkLabel: 'En savoir plus', link: '/services/' },
586
+ { icon: 'shield', title: 'Qualité', text: 'Des standards élevés, de A à Z.', linkLabel: 'En savoir plus', link: '/services/' },
587
+ { icon: 'heart', title: 'Proximité', text: 'Une équipe joignable et réactive.', linkLabel: 'En savoir plus', link: '/services/' },
588
+ ],
589
+ },
590
+ },
591
+ 2
592
+ );
593
+ if (cards) blocks.splice(2, 0, cards);
594
+ }
595
+ if (!blocks.some((b) => b.type === 'textMedia')) {
596
+ const tm = normalizeBlock(
597
+ {
598
+ type: 'textMedia',
599
+ data: {
600
+ title: `Pourquoi ${str(o.siteName, 40) || 'nous'}`,
601
+ intro: str(o.tagline, 200) || 'Une équipe engagée à vos côtés.',
602
+ bullets: ['Expertise terrain', 'Communication claire', 'Résultats mesurables'],
603
+ },
604
+ },
605
+ 3
606
+ );
607
+ if (tm) {
608
+ const afterCards = blocks.findIndex((b) => b.type === 'cards');
609
+ blocks.splice(afterCards >= 0 ? afterCards + 1 : blocks.length, 0, tm);
610
+ }
611
+ }
612
+ if (!blocks.some((b) => b.type === 'steps') && !blocks.some((b) => b.type === 'showcase')) {
613
+ const steps = normalizeBlock(
614
+ {
615
+ type: 'steps',
616
+ data: {
617
+ title: 'Comment ça se passe',
618
+ items: [
619
+ { title: 'Échange', text: 'Nous clarifions vos objectifs et contraintes.' },
620
+ { title: 'Proposition', text: 'Un plan d’action clair, avec un devis transparent.' },
621
+ { title: 'Réalisation', text: 'Livraison soignée et suivi jusqu’au résultat.' },
622
+ ],
623
+ },
624
+ },
625
+ 4
626
+ );
627
+ if (steps) {
628
+ const afterTm = blocks.findIndex((b) => b.type === 'textMedia');
629
+ blocks.splice(afterTm >= 0 ? afterTm + 1 : blocks.length, 0, steps);
630
+ }
631
+ }
632
+ if (!blocks.some((b) => b.type === 'testimonials') && !blocks.some((b) => b.type === 'faq')) {
633
+ const testimonials = normalizeBlock(
634
+ {
635
+ type: 'testimonials',
636
+ data: {
637
+ title: 'Ils nous recommandent',
638
+ items: [
639
+ { quote: 'Un accompagnement pro, réactif et vraiment à l’écoute.', author: 'Client satisfait', role: 'Dirigeant' },
640
+ { quote: 'Des résultats concrets, sans jargon inutile.', author: 'Partenaire', role: 'Responsable projet' },
641
+ ],
642
+ },
643
+ },
644
+ 5
645
+ );
646
+ if (testimonials) {
647
+ const beforeCta = blocks.findIndex((b) => b.type === 'cta');
648
+ blocks.splice(beforeCta >= 0 ? beforeCta : blocks.length, 0, testimonials);
649
+ }
650
+ }
651
+ if (!blocks.some((b) => b.type === 'cta')) {
652
+ const cta = normalizeBlock(
653
+ {
654
+ type: 'cta',
655
+ data: {
656
+ title: 'Parlons de votre projet',
657
+ text: str(o.tagline, 200),
658
+ highlight: 'dès aujourd’hui',
659
+ buttons: [
660
+ { label: 'Nous contacter', link: '/contact/', variant: 'primary', icon: 'arrow' },
661
+ { label: 'Nos services', link: '/services/', variant: 'light', icon: 'arrow-up-right' },
662
+ ],
663
+ },
664
+ },
665
+ blocks.length
666
+ );
667
+ if (cta) blocks.push(cta);
668
+ }
669
+
670
+ blocks = polishStructure(blocks);
671
+
672
+ return {
673
+ siteName: str(o.siteName, 80) || 'Mon site',
674
+ tagline: str(o.tagline, 160) || '',
675
+ industry: str(o.industry, 80) || '',
676
+ mood: str(o.mood, 80) || '',
677
+ rationale: str(o.rationale, 600) || '',
678
+ branding: {
679
+ colors: {
680
+ primary: hex(colorsIn.primary, base.primary),
681
+ primaryHover: hex(colorsIn.primaryHover, base.primaryHover),
682
+ accent: hex(colorsIn.accent, base.accent),
683
+ navy: hex(colorsIn.navy, base.navy),
684
+ background: hex(colorsIn.background, base.background),
685
+ text: hex(colorsIn.text, base.text),
686
+ muted: hex(colorsIn.muted, base.muted),
687
+ border: hex(colorsIn.border, base.border),
688
+ },
689
+ fonts: {
690
+ heading: font(fontsIn.heading, 'Plus Jakarta Sans'),
691
+ body: font(fontsIn.body, 'Manrope'),
692
+ },
693
+ },
694
+ themeSettings: {
695
+ stickyHeader: bool(settingsIn.stickyHeader, settings.stickyHeader),
696
+ headerPhone: bool(settingsIn.headerPhone, settings.headerPhone),
697
+ buttonShape: shape === 'rounded' || shape === 'square' ? shape : 'pill',
698
+ cardRadius,
699
+ density: density === 'compact' || density === 'airy' ? density : 'comfortable',
700
+ animations: bool(settingsIn.animations, settings.animations),
701
+ mobileBar: bool(settingsIn.mobileBar, settings.mobileBar),
702
+ backToTop: bool(settingsIn.backToTop, true),
703
+ },
704
+ homeBlocks: blocks,
705
+ };
706
+ }
707
+
708
+ export function parseModelJson(text: string): unknown {
709
+ const cleaned = text.replace(/^```json\s*/i, '').replace(/^```\s*/i, '').replace(/```$/i, '').trim();
710
+ const start = cleaned.indexOf('{');
711
+ const end = cleaned.lastIndexOf('}');
712
+ if (start < 0 || end <= start) throw new Error('Réponse IA invalide (JSON attendu).');
713
+ return JSON.parse(cleaned.slice(start, end + 1));
714
+ }