create-smoodly-app 0.0.45 → 0.0.47

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 (395) hide show
  1. package/README.md +15 -10
  2. package/dist/index.js +75 -33
  3. package/package.json +2 -2
  4. package/templates/astro-blank/AGENTS.md +55 -0
  5. package/templates/astro-blank/CLAUDE.md +1 -0
  6. package/templates/astro-blank/README.md +88 -0
  7. package/templates/astro-blank/_gitignore +5 -0
  8. package/templates/astro-blank/astro.config.ts +15 -0
  9. package/templates/astro-blank/package.json +24 -0
  10. package/templates/astro-blank/scripts/seed.ts +80 -0
  11. package/templates/astro-blank/src/components/LocaleNav.astro +15 -0
  12. package/templates/astro-blank/src/components/elements/Button.schema.ts +22 -0
  13. package/templates/astro-blank/src/components/pages/Home.schema.ts +17 -0
  14. package/templates/astro-blank/src/components/pages/Page.schema.ts +14 -0
  15. package/templates/astro-blank/src/components/sections/Text.schema.ts +24 -0
  16. package/templates/astro-blank/src/elements/button.astro +10 -0
  17. package/templates/astro-blank/src/entry-pages/posts.astro +23 -0
  18. package/templates/astro-blank/src/layouts/Layout.astro +25 -0
  19. package/templates/astro-blank/src/live.config.ts +13 -0
  20. package/templates/astro-blank/src/page-types/home.astro +15 -0
  21. package/templates/astro-blank/src/page-types/page.astro +15 -0
  22. package/templates/astro-blank/src/pages/404.astro +7 -0
  23. package/templates/astro-blank/src/sections/text.astro +18 -0
  24. package/templates/astro-blank/src/smoodly/collections.ts +24 -0
  25. package/templates/astro-blank/src/smoodly/config.ts +28 -0
  26. package/templates/astro-blank/src/smoodly/elements.ts +5 -0
  27. package/templates/astro-blank/src/smoodly/globals.ts +18 -0
  28. package/templates/astro-blank/src/smoodly/sections.ts +5 -0
  29. package/templates/astro-blank/src/smoodly/toolsets.ts +14 -0
  30. package/templates/astro-blank/src/styles/global.css +57 -0
  31. package/templates/astro-blank/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  32. package/templates/astro-blank/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  33. package/templates/astro-blank/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  34. package/templates/astro-blank/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  35. package/templates/astro-blank/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  36. package/templates/astro-blank/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  37. package/templates/astro-blank/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  38. package/templates/astro-blank/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  39. package/templates/astro-blank/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  40. package/templates/astro-blank/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  41. package/templates/{blank/supabase/migrations/20260930041056_smoodly_views.sql → astro-blank/supabase/migrations/20261008120100_smoodly_views.sql} +1 -1
  42. package/templates/astro-blank/tsconfig.json +17 -0
  43. package/templates/astro-blank-intl/AGENTS.md +55 -0
  44. package/templates/astro-blank-intl/CLAUDE.md +1 -0
  45. package/templates/astro-blank-intl/README.md +89 -0
  46. package/templates/astro-blank-intl/_gitignore +5 -0
  47. package/templates/astro-blank-intl/astro.config.ts +17 -0
  48. package/templates/astro-blank-intl/package.json +24 -0
  49. package/templates/astro-blank-intl/scripts/seed.ts +80 -0
  50. package/templates/astro-blank-intl/src/components/LocaleNav.astro +15 -0
  51. package/templates/astro-blank-intl/src/components/elements/Button.schema.ts +22 -0
  52. package/templates/astro-blank-intl/src/components/pages/Home.schema.ts +17 -0
  53. package/templates/astro-blank-intl/src/components/pages/Page.schema.ts +14 -0
  54. package/templates/astro-blank-intl/src/components/sections/Text.schema.ts +24 -0
  55. package/templates/astro-blank-intl/src/elements/button.astro +10 -0
  56. package/templates/astro-blank-intl/src/entry-pages/posts.astro +23 -0
  57. package/templates/astro-blank-intl/src/layouts/Layout.astro +25 -0
  58. package/templates/astro-blank-intl/src/live.config.ts +13 -0
  59. package/templates/astro-blank-intl/src/page-types/home.astro +15 -0
  60. package/templates/astro-blank-intl/src/page-types/page.astro +15 -0
  61. package/templates/astro-blank-intl/src/pages/404.astro +7 -0
  62. package/templates/astro-blank-intl/src/sections/text.astro +18 -0
  63. package/templates/astro-blank-intl/src/smoodly/collections.ts +24 -0
  64. package/templates/astro-blank-intl/src/smoodly/config.ts +29 -0
  65. package/templates/astro-blank-intl/src/smoodly/elements.ts +5 -0
  66. package/templates/astro-blank-intl/src/smoodly/globals.ts +18 -0
  67. package/templates/astro-blank-intl/src/smoodly/sections.ts +5 -0
  68. package/templates/astro-blank-intl/src/smoodly/toolsets.ts +14 -0
  69. package/templates/astro-blank-intl/src/styles/global.css +57 -0
  70. package/templates/astro-blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  71. package/templates/astro-blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  72. package/templates/astro-blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  73. package/templates/astro-blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  74. package/templates/astro-blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  75. package/templates/astro-blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  76. package/templates/astro-blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  77. package/templates/astro-blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  78. package/templates/astro-blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  79. package/templates/astro-blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  80. package/templates/{blank-intl/supabase/migrations/20260930041056_smoodly_views.sql → astro-blank-intl/supabase/migrations/20261008120100_smoodly_views.sql} +1 -1
  81. package/templates/astro-blank-intl/tsconfig.json +17 -0
  82. package/templates/astro-blank-intl.tsconfig.json +24 -0
  83. package/templates/astro-blank.tsconfig.json +24 -0
  84. package/templates/astro-lorem/AGENTS.md +55 -0
  85. package/templates/astro-lorem/CLAUDE.md +1 -0
  86. package/templates/astro-lorem/README.md +60 -0
  87. package/templates/astro-lorem/_gitignore +5 -0
  88. package/templates/astro-lorem/astro.config.ts +15 -0
  89. package/templates/astro-lorem/package.json +24 -0
  90. package/templates/astro-lorem/public/images/about_main.jpg +0 -0
  91. package/templates/astro-lorem/public/images/article_photo.jpg +0 -0
  92. package/templates/astro-lorem/public/images/hero_image.jpg +0 -0
  93. package/templates/astro-lorem/public/images/home_section_2.jpg +0 -0
  94. package/templates/astro-lorem/scripts/seed.ts +405 -0
  95. package/templates/astro-lorem/src/components/ArticleCard.astro +19 -0
  96. package/templates/astro-lorem/src/components/articles.ts +42 -0
  97. package/templates/astro-lorem/src/components/elements/Callout.schema.ts +24 -0
  98. package/templates/astro-lorem/src/components/elements/Cta.schema.ts +21 -0
  99. package/templates/astro-lorem/src/components/news.ts +13 -0
  100. package/templates/astro-lorem/src/components/pages/Articles.schema.ts +20 -0
  101. package/templates/astro-lorem/src/components/pages/Home.schema.ts +26 -0
  102. package/templates/astro-lorem/src/components/pages/News.schema.ts +17 -0
  103. package/templates/astro-lorem/src/components/pages/Page.schema.ts +24 -0
  104. package/templates/astro-lorem/src/components/sections/FavouriteArticles.schema.ts +17 -0
  105. package/templates/astro-lorem/src/components/sections/Footer.schema.ts +31 -0
  106. package/templates/astro-lorem/src/components/sections/Header.schema.ts +21 -0
  107. package/templates/astro-lorem/src/components/sections/Hero.schema.ts +24 -0
  108. package/templates/astro-lorem/src/components/sections/ImageText.schema.ts +30 -0
  109. package/templates/astro-lorem/src/components/sections/PageHero.schema.ts +21 -0
  110. package/templates/astro-lorem/src/components/sections/People.schema.ts +15 -0
  111. package/templates/astro-lorem/src/components/sections/Points.schema.ts +21 -0
  112. package/templates/astro-lorem/src/components/sections/Quote.schema.ts +23 -0
  113. package/templates/astro-lorem/src/elements/callout.astro +13 -0
  114. package/templates/astro-lorem/src/elements/cta.astro +15 -0
  115. package/templates/astro-lorem/src/entry-pages/articles.astro +71 -0
  116. package/templates/astro-lorem/src/entry-pages/news.astro +29 -0
  117. package/templates/astro-lorem/src/layouts/Layout.astro +39 -0
  118. package/templates/astro-lorem/src/live.config.ts +20 -0
  119. package/templates/astro-lorem/src/messages/en.json +7 -0
  120. package/templates/astro-lorem/src/messages/fi.json +7 -0
  121. package/templates/astro-lorem/src/page-types/articlesIndex.astro +24 -0
  122. package/templates/astro-lorem/src/page-types/home.astro +14 -0
  123. package/templates/astro-lorem/src/page-types/newsIndex.astro +28 -0
  124. package/templates/astro-lorem/src/page-types/page.astro +27 -0
  125. package/templates/astro-lorem/src/pages/404.astro +7 -0
  126. package/templates/astro-lorem/src/sections/favouriteArticles.astro +26 -0
  127. package/templates/astro-lorem/src/sections/footer.astro +37 -0
  128. package/templates/astro-lorem/src/sections/header.astro +27 -0
  129. package/templates/astro-lorem/src/sections/hero.astro +28 -0
  130. package/templates/astro-lorem/src/sections/imageText.astro +22 -0
  131. package/templates/astro-lorem/src/sections/pageHero.astro +20 -0
  132. package/templates/astro-lorem/src/sections/peopleSection.astro +24 -0
  133. package/templates/astro-lorem/src/sections/points.astro +32 -0
  134. package/templates/astro-lorem/src/sections/quote.astro +16 -0
  135. package/templates/astro-lorem/src/smoodly/collections.ts +63 -0
  136. package/templates/astro-lorem/src/smoodly/config.ts +48 -0
  137. package/templates/astro-lorem/src/smoodly/elements.ts +6 -0
  138. package/templates/astro-lorem/src/smoodly/globals.ts +29 -0
  139. package/templates/astro-lorem/src/smoodly/sections.ts +18 -0
  140. package/templates/astro-lorem/src/smoodly/toolsets.ts +16 -0
  141. package/templates/astro-lorem/src/strings.ts +17 -0
  142. package/templates/astro-lorem/src/styles/global.css +228 -0
  143. package/templates/astro-lorem/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  144. package/templates/astro-lorem/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  145. package/templates/astro-lorem/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  146. package/templates/astro-lorem/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  147. package/templates/astro-lorem/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  148. package/templates/astro-lorem/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  149. package/templates/astro-lorem/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  150. package/templates/astro-lorem/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  151. package/templates/astro-lorem/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  152. package/templates/astro-lorem/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  153. package/templates/{lorem/supabase/migrations/20260930041056_smoodly_views.sql → astro-lorem/supabase/migrations/20261008120100_smoodly_views.sql} +1 -1
  154. package/templates/astro-lorem/tsconfig.json +17 -0
  155. package/templates/astro-lorem-intl/AGENTS.md +55 -0
  156. package/templates/astro-lorem-intl/CLAUDE.md +1 -0
  157. package/templates/astro-lorem-intl/README.md +62 -0
  158. package/templates/astro-lorem-intl/_gitignore +5 -0
  159. package/templates/astro-lorem-intl/astro.config.ts +17 -0
  160. package/templates/astro-lorem-intl/package.json +24 -0
  161. package/templates/astro-lorem-intl/public/images/about_main.jpg +0 -0
  162. package/templates/astro-lorem-intl/public/images/article_photo.jpg +0 -0
  163. package/templates/astro-lorem-intl/public/images/hero_image.jpg +0 -0
  164. package/templates/astro-lorem-intl/public/images/home_section_2.jpg +0 -0
  165. package/templates/astro-lorem-intl/scripts/seed.ts +405 -0
  166. package/templates/astro-lorem-intl/src/components/ArticleCard.astro +19 -0
  167. package/templates/astro-lorem-intl/src/components/articles.ts +42 -0
  168. package/templates/astro-lorem-intl/src/components/elements/Callout.schema.ts +24 -0
  169. package/templates/astro-lorem-intl/src/components/elements/Cta.schema.ts +21 -0
  170. package/templates/astro-lorem-intl/src/components/news.ts +13 -0
  171. package/templates/astro-lorem-intl/src/components/pages/Articles.schema.ts +20 -0
  172. package/templates/astro-lorem-intl/src/components/pages/Home.schema.ts +26 -0
  173. package/templates/astro-lorem-intl/src/components/pages/News.schema.ts +17 -0
  174. package/templates/astro-lorem-intl/src/components/pages/Page.schema.ts +24 -0
  175. package/templates/astro-lorem-intl/src/components/sections/FavouriteArticles.schema.ts +17 -0
  176. package/templates/astro-lorem-intl/src/components/sections/Footer.schema.ts +31 -0
  177. package/templates/astro-lorem-intl/src/components/sections/Header.schema.ts +21 -0
  178. package/templates/astro-lorem-intl/src/components/sections/Hero.schema.ts +24 -0
  179. package/templates/astro-lorem-intl/src/components/sections/ImageText.schema.ts +30 -0
  180. package/templates/astro-lorem-intl/src/components/sections/PageHero.schema.ts +21 -0
  181. package/templates/astro-lorem-intl/src/components/sections/People.schema.ts +15 -0
  182. package/templates/astro-lorem-intl/src/components/sections/Points.schema.ts +21 -0
  183. package/templates/astro-lorem-intl/src/components/sections/Quote.schema.ts +23 -0
  184. package/templates/astro-lorem-intl/src/elements/callout.astro +13 -0
  185. package/templates/astro-lorem-intl/src/elements/cta.astro +15 -0
  186. package/templates/astro-lorem-intl/src/entry-pages/articles.astro +71 -0
  187. package/templates/astro-lorem-intl/src/entry-pages/news.astro +29 -0
  188. package/templates/astro-lorem-intl/src/layouts/Layout.astro +39 -0
  189. package/templates/astro-lorem-intl/src/live.config.ts +20 -0
  190. package/templates/astro-lorem-intl/src/messages/en.json +7 -0
  191. package/templates/astro-lorem-intl/src/messages/fi.json +7 -0
  192. package/templates/astro-lorem-intl/src/page-types/articlesIndex.astro +24 -0
  193. package/templates/astro-lorem-intl/src/page-types/home.astro +14 -0
  194. package/templates/astro-lorem-intl/src/page-types/newsIndex.astro +28 -0
  195. package/templates/astro-lorem-intl/src/page-types/page.astro +27 -0
  196. package/templates/astro-lorem-intl/src/pages/404.astro +7 -0
  197. package/templates/astro-lorem-intl/src/sections/favouriteArticles.astro +26 -0
  198. package/templates/astro-lorem-intl/src/sections/footer.astro +37 -0
  199. package/templates/astro-lorem-intl/src/sections/header.astro +27 -0
  200. package/templates/astro-lorem-intl/src/sections/hero.astro +28 -0
  201. package/templates/astro-lorem-intl/src/sections/imageText.astro +22 -0
  202. package/templates/astro-lorem-intl/src/sections/pageHero.astro +20 -0
  203. package/templates/astro-lorem-intl/src/sections/peopleSection.astro +24 -0
  204. package/templates/astro-lorem-intl/src/sections/points.astro +32 -0
  205. package/templates/astro-lorem-intl/src/sections/quote.astro +16 -0
  206. package/templates/astro-lorem-intl/src/smoodly/collections.ts +63 -0
  207. package/templates/astro-lorem-intl/src/smoodly/config.ts +48 -0
  208. package/templates/astro-lorem-intl/src/smoodly/elements.ts +6 -0
  209. package/templates/astro-lorem-intl/src/smoodly/globals.ts +29 -0
  210. package/templates/astro-lorem-intl/src/smoodly/sections.ts +18 -0
  211. package/templates/astro-lorem-intl/src/smoodly/toolsets.ts +16 -0
  212. package/templates/astro-lorem-intl/src/strings.ts +17 -0
  213. package/templates/astro-lorem-intl/src/styles/global.css +228 -0
  214. package/templates/astro-lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +535 -0
  215. package/templates/astro-lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +26 -0
  216. package/templates/astro-lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +38 -0
  217. package/templates/astro-lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +41 -0
  218. package/templates/astro-lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +56 -0
  219. package/templates/astro-lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +200 -0
  220. package/templates/astro-lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +84 -0
  221. package/templates/astro-lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  222. package/templates/astro-lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  223. package/templates/astro-lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  224. package/templates/{lorem-intl/supabase/migrations/20260930041056_smoodly_views.sql → astro-lorem-intl/supabase/migrations/20261008120100_smoodly_views.sql} +1 -1
  225. package/templates/astro-lorem-intl/tsconfig.json +17 -0
  226. package/templates/astro-lorem-intl.tsconfig.json +24 -0
  227. package/templates/astro-lorem.tsconfig.json +24 -0
  228. package/templates/blank/AGENTS.md +23 -16
  229. package/templates/blank/README.md +20 -15
  230. package/templates/blank/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  231. package/templates/blank/components/elements/Button.schema.ts +22 -0
  232. package/templates/blank/components/elements/Button.tsx +2 -20
  233. package/templates/blank/components/pages/Home.schema.ts +17 -0
  234. package/templates/blank/components/pages/Home.tsx +2 -16
  235. package/templates/blank/components/pages/Page.schema.ts +14 -0
  236. package/templates/blank/components/pages/Page.tsx +2 -14
  237. package/templates/blank/components/pages/Post.tsx +5 -4
  238. package/templates/blank/components/sections/Text.schema.ts +24 -0
  239. package/templates/blank/components/sections/Text.tsx +7 -27
  240. package/templates/blank/package.json +1 -1
  241. package/templates/blank/scripts/seed.ts +2 -2
  242. package/templates/blank/smoodly/config.ts +8 -7
  243. package/templates/blank/smoodly/elements.ts +4 -4
  244. package/templates/blank/smoodly/globals.ts +4 -2
  245. package/templates/blank/smoodly/richtext.tsx +4 -5
  246. package/templates/blank/smoodly/sections.ts +4 -4
  247. package/templates/blank/smoodly/server.ts +22 -44
  248. package/templates/blank/smoodly/toolsets.ts +2 -2
  249. package/templates/blank/smoodly/views.ts +15 -0
  250. package/templates/blank/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  251. package/templates/blank/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  252. package/templates/blank/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  253. package/templates/blank/supabase/migrations/20261008120100_smoodly_views.sql +20 -0
  254. package/templates/blank-intl/AGENTS.md +23 -16
  255. package/templates/blank-intl/README.md +20 -15
  256. package/templates/blank-intl/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  257. package/templates/blank-intl/components/elements/Button.schema.ts +22 -0
  258. package/templates/blank-intl/components/elements/Button.tsx +2 -20
  259. package/templates/blank-intl/components/pages/Home.schema.ts +17 -0
  260. package/templates/blank-intl/components/pages/Home.tsx +2 -16
  261. package/templates/blank-intl/components/pages/Page.schema.ts +14 -0
  262. package/templates/blank-intl/components/pages/Page.tsx +2 -14
  263. package/templates/blank-intl/components/pages/Post.tsx +5 -4
  264. package/templates/blank-intl/components/sections/Text.schema.ts +24 -0
  265. package/templates/blank-intl/components/sections/Text.tsx +7 -27
  266. package/templates/blank-intl/package.json +1 -1
  267. package/templates/blank-intl/scripts/seed.ts +2 -2
  268. package/templates/blank-intl/smoodly/config.ts +8 -7
  269. package/templates/blank-intl/smoodly/elements.ts +4 -4
  270. package/templates/blank-intl/smoodly/globals.ts +4 -2
  271. package/templates/blank-intl/smoodly/richtext.tsx +4 -5
  272. package/templates/blank-intl/smoodly/sections.ts +4 -4
  273. package/templates/blank-intl/smoodly/server.ts +22 -44
  274. package/templates/blank-intl/smoodly/toolsets.ts +2 -2
  275. package/templates/blank-intl/smoodly/views.ts +15 -0
  276. package/templates/blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  277. package/templates/blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  278. package/templates/blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  279. package/templates/blank-intl/supabase/migrations/20261008120100_smoodly_views.sql +20 -0
  280. package/templates/lorem/AGENTS.md +23 -16
  281. package/templates/lorem/README.md +3 -3
  282. package/templates/lorem/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  283. package/templates/lorem/components/elements/Callout.schema.ts +24 -0
  284. package/templates/lorem/components/elements/Callout.tsx +2 -24
  285. package/templates/lorem/components/elements/Cta.schema.ts +21 -0
  286. package/templates/lorem/components/elements/Cta.tsx +2 -21
  287. package/templates/lorem/components/pages/Article.tsx +1 -1
  288. package/templates/lorem/components/pages/Articles.schema.ts +20 -0
  289. package/templates/lorem/components/pages/Articles.tsx +2 -20
  290. package/templates/lorem/components/pages/Home.schema.ts +26 -0
  291. package/templates/lorem/components/pages/Home.tsx +2 -26
  292. package/templates/lorem/components/pages/News.schema.ts +17 -0
  293. package/templates/lorem/components/pages/News.tsx +2 -17
  294. package/templates/lorem/components/pages/NewsPost.tsx +1 -1
  295. package/templates/lorem/components/pages/Page.schema.ts +24 -0
  296. package/templates/lorem/components/pages/Page.tsx +2 -24
  297. package/templates/lorem/components/sections/FavouriteArticles.schema.ts +17 -0
  298. package/templates/lorem/components/sections/FavouriteArticles.tsx +2 -17
  299. package/templates/lorem/components/sections/Footer.schema.ts +31 -0
  300. package/templates/lorem/components/sections/Footer.tsx +4 -32
  301. package/templates/lorem/components/sections/Header.schema.ts +21 -0
  302. package/templates/lorem/components/sections/Header.tsx +2 -21
  303. package/templates/lorem/components/sections/Hero.schema.ts +24 -0
  304. package/templates/lorem/components/sections/Hero.tsx +2 -24
  305. package/templates/lorem/components/sections/ImageText.schema.ts +30 -0
  306. package/templates/lorem/components/sections/ImageText.tsx +2 -30
  307. package/templates/lorem/components/sections/PageHero.schema.ts +21 -0
  308. package/templates/lorem/components/sections/PageHero.tsx +2 -21
  309. package/templates/lorem/components/sections/People.schema.ts +15 -0
  310. package/templates/lorem/components/sections/People.tsx +2 -14
  311. package/templates/lorem/components/sections/Points.schema.ts +21 -0
  312. package/templates/lorem/components/sections/Points.tsx +2 -21
  313. package/templates/lorem/components/sections/Quote.schema.ts +23 -0
  314. package/templates/lorem/components/sections/Quote.tsx +2 -23
  315. package/templates/lorem/package.json +1 -1
  316. package/templates/lorem/scripts/seed.ts +18 -18
  317. package/templates/lorem/smoodly/collections.ts +2 -2
  318. package/templates/lorem/smoodly/config.ts +14 -15
  319. package/templates/lorem/smoodly/elements.ts +5 -5
  320. package/templates/lorem/smoodly/globals.ts +10 -7
  321. package/templates/lorem/smoodly/richtext.tsx +5 -5
  322. package/templates/lorem/smoodly/sections.ts +12 -13
  323. package/templates/lorem/smoodly/server.ts +22 -44
  324. package/templates/lorem/smoodly/toolsets.ts +3 -3
  325. package/templates/lorem/smoodly/views.ts +33 -0
  326. package/templates/lorem/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  327. package/templates/lorem/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  328. package/templates/lorem/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  329. package/templates/lorem/supabase/migrations/20261008120100_smoodly_views.sql +58 -0
  330. package/templates/lorem-intl/AGENTS.md +23 -16
  331. package/templates/lorem-intl/README.md +3 -3
  332. package/templates/lorem-intl/app/(smoodly)/admin/[[...segments]]/route.ts +8 -0
  333. package/templates/lorem-intl/components/elements/Callout.schema.ts +24 -0
  334. package/templates/lorem-intl/components/elements/Callout.tsx +2 -24
  335. package/templates/lorem-intl/components/elements/Cta.schema.ts +21 -0
  336. package/templates/lorem-intl/components/elements/Cta.tsx +2 -21
  337. package/templates/lorem-intl/components/pages/Article.tsx +1 -1
  338. package/templates/lorem-intl/components/pages/Articles.schema.ts +20 -0
  339. package/templates/lorem-intl/components/pages/Articles.tsx +2 -20
  340. package/templates/lorem-intl/components/pages/Home.schema.ts +26 -0
  341. package/templates/lorem-intl/components/pages/Home.tsx +2 -26
  342. package/templates/lorem-intl/components/pages/News.schema.ts +17 -0
  343. package/templates/lorem-intl/components/pages/News.tsx +2 -17
  344. package/templates/lorem-intl/components/pages/NewsPost.tsx +1 -1
  345. package/templates/lorem-intl/components/pages/Page.schema.ts +24 -0
  346. package/templates/lorem-intl/components/pages/Page.tsx +2 -24
  347. package/templates/lorem-intl/components/sections/FavouriteArticles.schema.ts +17 -0
  348. package/templates/lorem-intl/components/sections/FavouriteArticles.tsx +2 -17
  349. package/templates/lorem-intl/components/sections/Footer.schema.ts +31 -0
  350. package/templates/lorem-intl/components/sections/Footer.tsx +4 -32
  351. package/templates/lorem-intl/components/sections/Header.schema.ts +21 -0
  352. package/templates/lorem-intl/components/sections/Header.tsx +2 -21
  353. package/templates/lorem-intl/components/sections/Hero.schema.ts +24 -0
  354. package/templates/lorem-intl/components/sections/Hero.tsx +2 -24
  355. package/templates/lorem-intl/components/sections/ImageText.schema.ts +30 -0
  356. package/templates/lorem-intl/components/sections/ImageText.tsx +2 -30
  357. package/templates/lorem-intl/components/sections/PageHero.schema.ts +21 -0
  358. package/templates/lorem-intl/components/sections/PageHero.tsx +2 -21
  359. package/templates/lorem-intl/components/sections/People.schema.ts +15 -0
  360. package/templates/lorem-intl/components/sections/People.tsx +2 -14
  361. package/templates/lorem-intl/components/sections/Points.schema.ts +21 -0
  362. package/templates/lorem-intl/components/sections/Points.tsx +2 -21
  363. package/templates/lorem-intl/components/sections/Quote.schema.ts +23 -0
  364. package/templates/lorem-intl/components/sections/Quote.tsx +2 -23
  365. package/templates/lorem-intl/package.json +1 -1
  366. package/templates/lorem-intl/scripts/seed.ts +18 -18
  367. package/templates/lorem-intl/smoodly/collections.ts +2 -2
  368. package/templates/lorem-intl/smoodly/config.ts +14 -15
  369. package/templates/lorem-intl/smoodly/elements.ts +5 -5
  370. package/templates/lorem-intl/smoodly/globals.ts +10 -7
  371. package/templates/lorem-intl/smoodly/richtext.tsx +5 -5
  372. package/templates/lorem-intl/smoodly/sections.ts +12 -13
  373. package/templates/lorem-intl/smoodly/server.ts +22 -44
  374. package/templates/lorem-intl/smoodly/toolsets.ts +3 -3
  375. package/templates/lorem-intl/smoodly/views.ts +33 -0
  376. package/templates/lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +154 -0
  377. package/templates/lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +30 -0
  378. package/templates/lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +47 -0
  379. package/templates/lorem-intl/supabase/migrations/20261008120100_smoodly_views.sql +58 -0
  380. package/templates/blank/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  381. package/templates/blank/app/(smoodly)/admin/layout.tsx +0 -15
  382. package/templates/blank/smoodly/admin.tsx +0 -6
  383. package/templates/blank/smoodly/ops.ts +0 -11
  384. package/templates/blank-intl/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  385. package/templates/blank-intl/app/(smoodly)/admin/layout.tsx +0 -15
  386. package/templates/blank-intl/smoodly/admin.tsx +0 -6
  387. package/templates/blank-intl/smoodly/ops.ts +0 -11
  388. package/templates/lorem/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  389. package/templates/lorem/app/(smoodly)/admin/layout.tsx +0 -15
  390. package/templates/lorem/smoodly/admin.tsx +0 -6
  391. package/templates/lorem/smoodly/ops.ts +0 -11
  392. package/templates/lorem-intl/app/(smoodly)/admin/[[...segments]]/page.tsx +0 -1
  393. package/templates/lorem-intl/app/(smoodly)/admin/layout.tsx +0 -15
  394. package/templates/lorem-intl/smoodly/admin.tsx +0 -6
  395. package/templates/lorem-intl/smoodly/ops.ts +0 -11
@@ -0,0 +1,31 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // The site footer — a VISUAL global item (smoodly/globals.ts): one row,
4
+ // edited under Global content, rendered by the layout. Three link columns
5
+ // as top-level objects, each localized as a whole (an object holding a
6
+ // list — one level, which the list field allows; a list of columns each
7
+ // holding a list would be a nested list, which it refuses).
8
+ const column = () =>
9
+ f.object({ title: f.text(), links: f.list(f.object({ label: f.text(), href: f.link() })) }).localized();
10
+
11
+ export const footerSchema = smoodly.schema.section({
12
+ name: "footer",
13
+ title: "Footer",
14
+ description: "The site footer: tagline, three link columns, copyright.",
15
+ fields: {
16
+ tagline: f.text().localized(),
17
+ company: column(),
18
+ legal: column(),
19
+ social: column(),
20
+ copyright: f.text(),
21
+ },
22
+ sample: {
23
+ tagline: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
24
+ company: { title: "Company", links: [{ label: "About", href: "/company/about-us" }] },
25
+ legal: { title: "Legal", links: [{ label: "Privacy", href: "#" }] },
26
+ social: { title: "Social", links: [{ label: "Instagram", href: "#" }] },
27
+ copyright: "© 2026 Lorem Co. Built with Smoodly.",
28
+ },
29
+ // The layout's footer keeps the design's spacing: no Spacing & layout group.
30
+ styles: false,
31
+ });
@@ -0,0 +1,21 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // The site header — a VISUAL global item (smoodly/globals.ts): one row,
4
+ // edited under Global content, rendered by the layout, never placed in a
5
+ // page. The brand is the same in every language; the nav is a list of
6
+ // links localized as a whole, so each language has its own labels and
7
+ // hrefs. The layout renders HeaderView directly to hand it the locale
8
+ // links (a paired component takes only its schema's props); the paired
9
+ // Header is what the registry and the admin know.
10
+ export const headerSchema = smoodly.schema.section({
11
+ name: "header",
12
+ title: "Header",
13
+ description: "The site header: brand and navigation.",
14
+ fields: {
15
+ brand: f.text(),
16
+ nav: f.list(f.object({ label: f.text(), href: f.link() })).localized(),
17
+ },
18
+ sample: { brand: "Lorem Co.", nav: [{ label: "Home", href: "/" }] },
19
+ // Nobody pads the header from the editor: no Spacing & layout group.
20
+ styles: false,
21
+ });
@@ -0,0 +1,24 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // The home page opener: kicker, heading, text, up to two buttons and a
4
+ // portrait photo. Home locks it (`z.locked(heroSchema)`): one node an editor
5
+ // fills but cannot add, move or remove. `sample` is what the editor
6
+ // materializes into an empty locked zone, so it is a complete value.
7
+ export const heroSchema = smoodly.schema.section({
8
+ name: "hero",
9
+ title: "Hero",
10
+ description: "The home page opener: heading, text, buttons and a photo.",
11
+ fields: {
12
+ kicker: f.text().optional(),
13
+ heading: f.text(),
14
+ text: f.text(),
15
+ buttons: f.list(f.object({ label: f.text(), href: f.link() })).max(2),
16
+ image: f.image({ aspect: "4:5" }),
17
+ },
18
+ sample: {
19
+ kicker: "Lorem ipsum dolor",
20
+ heading: "Consectetur adipiscing elit sed do eiusmod",
21
+ text: "Tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.",
22
+ buttons: [{ label: "About us", href: "/company/about-us" }],
23
+ },
24
+ });
@@ -0,0 +1,30 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // A photo beside a heading and richtext, the photo on either side (a
4
+ // setting, not a field: settings are never translated). Without a photo the
5
+ // heading takes the left column and the text the right — the About
6
+ // page's text block. The richtext renders through the package's
7
+ // RichText: paragraphs, headings, lists, quotes.
8
+ export const imageTextSchema = smoodly.schema.section({
9
+ name: "imageText",
10
+ title: "Image and text",
11
+ description: "A photo beside a heading and text; text alone in two columns without one.",
12
+ fields: {
13
+ image: f.image({ aspect: "4:3" }).optional(),
14
+ heading: f.text(),
15
+ // Body copy beside a photo: emphasis and links, no headings — the block's
16
+ // own heading field owns that. Every other richtext field takes the default.
17
+ text: f.richtext({ toolset: smoodly.toolset({ marks: ["bold", "italic", "link"] }) }),
18
+ link: f.object({ label: f.text(), href: f.link() }).optional(),
19
+ },
20
+ settings: {
21
+ imageSide: f.radio([{ value: "left", label: "Image left" }, { value: "right", label: "Image right" }]).default("left"),
22
+ },
23
+ sample: {
24
+ heading: "Duis aute irure dolor in reprehenderit",
25
+ text: {
26
+ type: "doc",
27
+ content: [{ type: "paragraph", content: [{ type: "text", text: "In voluptate velit esse cillum dolore eu fugiat nulla pariatur." }] }],
28
+ },
29
+ },
30
+ });
@@ -0,0 +1,21 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // The opener of every page that is not the home page: kicker, heading,
4
+ // text and an optional wide photo. Page and Articles lock it — each page
5
+ // shape locks its own hero, which is the point: a locked zone is per
6
+ // shape. Without an image the copy stands alone (the Articles index).
7
+ export const pageHeroSchema = smoodly.schema.section({
8
+ name: "pageHero",
9
+ title: "Page hero",
10
+ description: "A page opener: kicker, heading, text and an optional wide photo.",
11
+ fields: {
12
+ kicker: f.text().optional(),
13
+ heading: f.text(),
14
+ text: f.text(),
15
+ image: f.image({ aspect: "21:9" }).optional(),
16
+ },
17
+ sample: {
18
+ heading: "About us",
19
+ text: "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
20
+ },
21
+ });
@@ -0,0 +1,15 @@
1
+ import { f, smoodly } from "smoodly";
2
+ import { people } from "@/smoodly/collections";
3
+
4
+ // People picked by hand (refList), four to a row: name and role, the
5
+ // role in the page's language because it is a localized field.
6
+ export const peopleSchema = smoodly.schema.section({
7
+ name: "peopleSection",
8
+ title: "People",
9
+ description: "A heading and a row of people.",
10
+ fields: {
11
+ heading: f.text(),
12
+ people: f.refList(() => people),
13
+ },
14
+ sample: { heading: "Our people", people: [] },
15
+ });
@@ -0,0 +1,21 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // A heading over one to six points, each an icon, a heading and a line
4
+ // of text: the list field with min/max, and an object inside the list.
5
+ export const pointsSchema = smoodly.schema.section({
6
+ name: "points",
7
+ title: "Points",
8
+ description: "A heading and one to six points with an icon each.",
9
+ fields: {
10
+ heading: f.text(),
11
+ points: f.list(f.object({
12
+ icon: f.select([{ value: "circle", label: "Circle" }, { value: "square", label: "Square" }, { value: "diamond", label: "Diamond" }]),
13
+ heading: f.text(),
14
+ text: f.text(),
15
+ })).min(1).max(6),
16
+ },
17
+ sample: {
18
+ heading: "Quis autem vel eum",
19
+ points: [{ icon: "circle", heading: "Iure reprehenderit", text: "Qui in ea voluptate velit esse quam nihil molestiae consequatur." }],
20
+ },
21
+ });
@@ -0,0 +1,23 @@
1
+ import { f, smoodly } from "smoodly";
2
+
3
+ // A pull quote. Registered as a global item (smoodly/globals.ts): one row
4
+ // edited under Global content, placed on pages from the "+" picker's
5
+ // Global group as a linked node. The body zones also list it, because
6
+ // the picker offers a global item only where the zone allows its
7
+ // section — so an editor may also drop a one-off quote.
8
+ export const quoteSchema = smoodly.schema.section({
9
+ name: "quote",
10
+ title: "Quote",
11
+ description: "A pull quote with its attribution.",
12
+ fields: {
13
+ text: f.text().localized(),
14
+ attribution: f.text().localized(),
15
+ },
16
+ settings: {
17
+ background: f.radio([{ value: "paper", label: "Paper" }, { value: "ink", label: "Ink" }]).default("paper"),
18
+ },
19
+ sample: {
20
+ text: "Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit.",
21
+ attribution: "Jane Doe, Founder",
22
+ },
23
+ });
@@ -0,0 +1,13 @@
1
+ ---
2
+ // src/elements/callout.astro — the view of the element named "callout", inside an article body
3
+ import { type Props as SmoodlyProps } from "smoodly";
4
+ import { RichText } from "smoodly/astro/components";
5
+ import { calloutSchema } from "@/components/elements/Callout.schema";
6
+
7
+ type Props = SmoodlyProps<typeof calloutSchema>;
8
+ const { title, body, settings } = Astro.props;
9
+ ---
10
+ <aside class="callout" data-tone={settings.tone}>
11
+ <strong class="callout__title">{title}</strong>
12
+ <div class="callout__body"><RichText doc={body} /></div>
13
+ </aside>
@@ -0,0 +1,15 @@
1
+ ---
2
+ // src/elements/cta.astro — the view of the element named "cta"
3
+ import { safeHref, type Props as SmoodlyProps } from "smoodly";
4
+ import { ctaSchema } from "@/components/elements/Cta.schema";
5
+
6
+ type Props = SmoodlyProps<typeof ctaSchema>;
7
+ const { heading, text, label, href, settings } = Astro.props;
8
+ ---
9
+ <div class="cta" data-variant={settings.variant}>
10
+ <div class="cta__copy">
11
+ <strong class="cta__heading">{heading}</strong>
12
+ {text && <p class="cta__text">{text}</p>}
13
+ </div>
14
+ <a class={settings.variant === "outline" ? "button button--outline" : "button"} href={safeHref(href)}>{label}</a>
15
+ </div>
@@ -0,0 +1,71 @@
1
+ ---
2
+ // src/entry-pages/articles.astro — the entry page of the collection "articles", served at the
3
+ // collection's path (/posts/<slug>, /fi/artikkelit/<slug>). Fields only; refs arrive resolved (author is a
4
+ // person). "Related" is the newest other published articles; "More from <author>" filters on the shared
5
+ // author field in the store. The render locale and the alternates ride on the entry (spec 2026-09-15).
6
+ import { getLiveCollection } from "astro:content";
7
+ import { Image, RichText } from "smoodly/astro/components";
8
+ import { href } from "smoodly/astro/runtime";
9
+ import { articlesIndexPath, formatDate, readingMinutes } from "@/components/articles";
10
+ import type { EntryFields, EntryItem } from "smoodly";
11
+ import type { PageHead } from "smoodly/astro/runtime";
12
+ import type { articles } from "@/smoodly/collections";
13
+ import Layout from "../layouts/Layout.astro";
14
+ import ArticleCard from "../components/ArticleCard.astro";
15
+ import { ui } from "../strings";
16
+
17
+ type Props = { entry: EntryItem<EntryFields<typeof articles>>; head: PageHead };
18
+ const { entry, head } = Astro.props;
19
+ const { locale, locales } = entry;
20
+ const { t } = ui(locale);
21
+ const a = entry.fields;
22
+ const url = (path: string) => href(path, locale);
23
+ // Four newest, minus this one, so three remain even when it is among them; the store applies the limit.
24
+ const { entries: newest = [] } = await getLiveCollection("articles", { locale, limit: 4 });
25
+ const related = newest.map((e) => e.data).filter((r) => r.id !== entry.id).slice(0, 3);
26
+ const authorId = a.author?.id;
27
+ const { entries: byAuthor = [] } = authorId ? await getLiveCollection("articles", { locale, where: { author: authorId } }) : { entries: [] };
28
+ const more = byAuthor.map((e) => e.data).filter((r) => r.id !== entry.id);
29
+ const index = url(articlesIndexPath(locale));
30
+ ---
31
+ <Layout head={head} locale={locale} locales={locales}>
32
+ <article class="container article">
33
+ <a class="article__back" href={index}>← {t("allArticles")}</a>
34
+ <div class="article__head">
35
+ {a.tags && a.tags.length > 0 && (
36
+ <div class="article__tags">{a.tags.map((tag, i) => <span class={i === 0 ? "tag tag--accent" : "tag"}>{tag}</span>)}</div>
37
+ )}
38
+ <h1 class="article__title">{a.title}</h1>
39
+ <p class="article__byline">
40
+ <span class="article__avatar" aria-hidden="true"></span>
41
+ <span>
42
+ {a.author?.fields.name && <strong>{a.author.fields.name}</strong>}
43
+ {a.author?.fields.name && " · "}
44
+ {a.date && <>{formatDate(a.date, locale)} · </>}
45
+ {t("minRead", { minutes: readingMinutes(a.body) })}
46
+ </span>
47
+ </p>
48
+ </div>
49
+ {a.image && <Image image={a.image} aspect="21:9" class="article__image" sizes="(min-width: 900px) 800px, 100vw" priority />}
50
+ <div class="article__body prose prose--lead"><RichText doc={a.body} sizes="(min-width: 900px) 720px, 100vw" /></div>
51
+ </article>
52
+ {more.length > 0 && (
53
+ <section class="related" data-testid="more-by-author">
54
+ <div class="container">
55
+ <div class="related__head"><h2 class="h2">{t("moreFromAuthor", { name: a.author?.fields.name ?? "" })}</h2></div>
56
+ <div class="card-grid card-grid--3">{more.map((r) => <ArticleCard article={r.fields} url={url(r.path!)} locale={locale} compact />)}</div>
57
+ </div>
58
+ </section>
59
+ )}
60
+ {related.length > 0 && (
61
+ <section class="related">
62
+ <div class="container">
63
+ <div class="related__head">
64
+ <h2 class="h2">{t("relatedArticles")}</h2>
65
+ <a class="link-accent" href={index}>{t("allArticles")} →</a>
66
+ </div>
67
+ <div class="card-grid card-grid--3">{related.map((r) => <ArticleCard article={r.fields} url={url(r.path!)} locale={locale} compact />)}</div>
68
+ </div>
69
+ </section>
70
+ )}
71
+ </Layout>
@@ -0,0 +1,29 @@
1
+ ---
2
+ // src/entry-pages/news.astro — the entry page of the collection "news", at
3
+ // /company/news/<slug> and /fi/yritys/uutiset/<slug>: the collection's nested path, then the slug.
4
+ import { RichText } from "smoodly/astro/components";
5
+ import { href } from "smoodly/astro/runtime";
6
+ import { formatDate } from "@/components/articles";
7
+ import { newsIndexPath } from "@/components/news";
8
+ import type { EntryFields, EntryItem } from "smoodly";
9
+ import type { PageHead } from "smoodly/astro/runtime";
10
+ import type { news } from "@/smoodly/collections";
11
+ import Layout from "../layouts/Layout.astro";
12
+ import { ui } from "../strings";
13
+
14
+ type Props = { entry: EntryItem<EntryFields<typeof news>>; head: PageHead };
15
+ const { entry, head } = Astro.props;
16
+ const { locale, locales } = entry;
17
+ const { t } = ui(locale);
18
+ const n = entry.fields;
19
+ ---
20
+ <Layout head={head} locale={locale} locales={locales}>
21
+ <article class="container article">
22
+ <a class="article__back" href={href(newsIndexPath(locale), locale)}>← {t("allNews")}</a>
23
+ <div class="article__head">
24
+ <h1 class="article__title">{n.title}</h1>
25
+ {n.date && <p class="article__byline">{formatDate(n.date, locale)}</p>}
26
+ </div>
27
+ <div class="article__body prose prose--lead"><RichText doc={n.body} /></div>
28
+ </article>
29
+ </Layout>
@@ -0,0 +1,39 @@
1
+ ---
2
+ // src/layouts/Layout.astro
3
+ // The site's own document: every page type and entry page renders inside it. The header and the footer are
4
+ // global content, read as live entries per locale, null until published there; the language links come from
5
+ // the page's own `locales` map (spec 2026-09-15), so a layout here can know which page is on screen — Astro
6
+ // renders the page before its layout's slot is filled. Nothing of Smoodly's is written here but the head it
7
+ // knows; in a preview the integration adds what the editor needs to the response.
8
+ import { getLiveEntry } from "astro:content";
9
+ import { Head } from "smoodly/astro/components";
10
+ import { href, type PageHead } from "smoodly/astro/runtime";
11
+ import Header from "../sections/header.astro";
12
+ import Footer from "../sections/footer.astro";
13
+ import "../styles/global.css";
14
+
15
+ type Props = { head: PageHead; locale: string; locales?: Record<string, { path: string | null } | null> };
16
+ const { head, locale, locales = {} } = Astro.props;
17
+ const [{ entry: header }, { entry: footer }, { entry: contact }] = await Promise.all([
18
+ getLiveEntry("header", { locale }),
19
+ getLiveEntry("footer", { locale }),
20
+ getLiveEntry("contact", { locale }),
21
+ ]);
22
+ const codes = Object.keys(locales);
23
+ const links = codes.length > 1
24
+ ? codes.map((code) => ({ code, url: href(locales[code]?.path ?? "/", code), current: code === locale }))
25
+ : undefined;
26
+ ---
27
+ <!doctype html>
28
+ <html lang={locale}>
29
+ <head>
30
+ <meta charset="utf-8" />
31
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
32
+ <Head of={head} fallback="Lorem Co." />
33
+ </head>
34
+ <body>
35
+ {header && <Header {...header.data.fields} settings={header.data.settings} styles={header.data.styles} home={href("/", locale)} locales={links} />}
36
+ <main><slot /></main>
37
+ {footer && <Footer {...footer.data.fields} settings={footer.data.settings} styles={footer.data.styles} contact={contact?.data.fields} />}
38
+ </body>
39
+ </html>
@@ -0,0 +1,20 @@
1
+ // src/live.config.ts
2
+ // Smoodly's content as live collections (spec 2026-10-02 astro cloud fit §6): a loader per collection, global
3
+ // item and the pages, from the package; this file only names them. `getLiveCollection("articles", { locale })`
4
+ // in a view reads through the read layer, and the response is tagged by what it read.
5
+ import { defineLiveCollection } from "astro:content";
6
+ import { entriesLoader, globalLoader, pageLoader, pageTreeLoader } from "smoodly/astro/content";
7
+ import { articles, news, people } from "@/smoodly/collections";
8
+ import { contact, footer, header, quote } from "@/smoodly/globals";
9
+
10
+ export const collections = {
11
+ pages: defineLiveCollection({ loader: pageLoader() }),
12
+ pageTree: defineLiveCollection({ loader: pageTreeLoader() }),
13
+ articles: defineLiveCollection({ loader: entriesLoader(articles) }),
14
+ news: defineLiveCollection({ loader: entriesLoader(news) }),
15
+ people: defineLiveCollection({ loader: entriesLoader(people) }),
16
+ header: defineLiveCollection({ loader: globalLoader(header) }),
17
+ footer: defineLiveCollection({ loader: globalLoader(footer) }),
18
+ contact: defineLiveCollection({ loader: globalLoader(contact) }),
19
+ quote: defineLiveCollection({ loader: globalLoader(quote) }),
20
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "allArticles": "All articles",
3
+ "relatedArticles": "Related articles",
4
+ "moreFromAuthor": "More from {name}",
5
+ "minRead": "{minutes} min read",
6
+ "allNews": "All news"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "allArticles": "Kaikki artikkelit",
3
+ "relatedArticles": "Aiheeseen liittyvät artikkelit",
4
+ "moreFromAuthor": "Lisää kirjoittajalta {name}",
5
+ "minRead": "{minutes} min lukuaika",
6
+ "allNews": "Kaikki uutiset"
7
+ }
@@ -0,0 +1,24 @@
1
+ ---
2
+ // src/page-types/articlesIndex.astro — the view of the page type named "articlesIndex": the
3
+ // editor owns the hero; the grid reads the published articles as a live collection, newest first (the
4
+ // collection's `order`), and in the canvas the drafts too (the request scope reads drafts in a preview).
5
+ import { getLiveCollection } from "astro:content";
6
+ import { Zone } from "smoodly/astro/components";
7
+ import { href } from "smoodly/astro/runtime";
8
+ import type { PageContext } from "smoodly";
9
+ import type { PageHead, ZoneContent } from "smoodly/astro/runtime";
10
+ import Layout from "../layouts/Layout.astro";
11
+ import ArticleCard from "../components/ArticleCard.astro";
12
+
13
+ type Props = { page: PageContext; zones: Record<string, ZoneContent>; head: PageHead };
14
+ const { page, zones, head } = Astro.props;
15
+ const { entries = [] } = await getLiveCollection("articles", { locale: page.locale });
16
+ ---
17
+ <Layout head={head} locale={page.locale} locales={page.locales}>
18
+ <Zone of={zones.hero} />
19
+ <section class="container articles-index">
20
+ <div class="card-grid">
21
+ {entries.map(({ data: entry }) => <ArticleCard article={entry.fields} url={href(entry.path!, page.locale)} locale={page.locale} />)}
22
+ </div>
23
+ </section>
24
+ </Layout>
@@ -0,0 +1,14 @@
1
+ ---
2
+ // src/page-types/home.astro — the view of the page type named "home"
3
+ import { Zone } from "smoodly/astro/components";
4
+ import type { PageContext } from "smoodly";
5
+ import type { PageHead, ZoneContent } from "smoodly/astro/runtime";
6
+ import Layout from "../layouts/Layout.astro";
7
+
8
+ type Props = { page: PageContext; zones: Record<string, ZoneContent>; head: PageHead };
9
+ const { page, zones, head } = Astro.props;
10
+ ---
11
+ <Layout head={head} locale={page.locale} locales={page.locales}>
12
+ <Zone of={zones.hero} />
13
+ <Zone of={zones.body} />
14
+ </Layout>
@@ -0,0 +1,28 @@
1
+ ---
2
+ // src/page-types/newsIndex.astro — the view of the page type named "newsIndex"
3
+ import { getLiveCollection } from "astro:content";
4
+ import { Zone } from "smoodly/astro/components";
5
+ import { href } from "smoodly/astro/runtime";
6
+ import { formatDate } from "@/components/articles";
7
+ import type { PageContext } from "smoodly";
8
+ import type { PageHead, ZoneContent } from "smoodly/astro/runtime";
9
+ import Layout from "../layouts/Layout.astro";
10
+
11
+ type Props = { page: PageContext; zones: Record<string, ZoneContent>; head: PageHead };
12
+ const { page, zones, head } = Astro.props;
13
+ const { entries = [] } = await getLiveCollection("news", { locale: page.locale });
14
+ ---
15
+ <Layout head={head} locale={page.locale} locales={page.locales}>
16
+ <Zone of={zones.hero} />
17
+ <section class="container news-index">
18
+ <ul class="news-list">
19
+ {entries.map(({ data: { path, fields: n } }) => (
20
+ <li class="news-list__item">
21
+ <time class="news-list__date" datetime={n.date}>{formatDate(n.date, page.locale)}</time>
22
+ <a class="news-list__title" href={href(path!, page.locale)}>{n.title}</a>
23
+ {n.excerpt && <p class="news-list__excerpt">{n.excerpt}</p>}
24
+ </li>
25
+ ))}
26
+ </ul>
27
+ </section>
28
+ </Layout>
@@ -0,0 +1,27 @@
1
+ ---
2
+ // src/page-types/page.astro — the view of the page type named "page"
3
+ import { Zone } from "smoodly/astro/components";
4
+ import type { PageContext } from "smoodly";
5
+ import type { PageHead, ZoneContent } from "smoodly/astro/runtime";
6
+ import Layout from "../layouts/Layout.astro";
7
+
8
+ type Props = { page: PageContext; zones: Record<string, ZoneContent>; head: PageHead };
9
+ const { page, zones, head } = Astro.props;
10
+ // Sub-navigation: the published children that opted in through their page field.
11
+ const shown = page.children.filter((c) => c.hasPage && c.fields.showInNavigation === true);
12
+ ---
13
+ <Layout head={head} locale={page.locale} locales={page.locales}>
14
+ {page.ancestors.length > 0 && (
15
+ <nav class="container breadcrumb" aria-label="Breadcrumb">
16
+ {page.ancestors.map((a) => <span>{a.hasPage ? <a href={a.url}>{a.title}</a> : a.title} / </span>)}
17
+ {page.title}
18
+ </nav>
19
+ )}
20
+ {shown.length > 0 && (
21
+ <nav class="container subnav" aria-label="Pages in this section">
22
+ {shown.map((c) => <a href={c.url}>{c.title}</a>)}
23
+ </nav>
24
+ )}
25
+ <Zone of={zones.hero} />
26
+ <Zone of={zones.body} />
27
+ </Layout>
@@ -0,0 +1,7 @@
1
+ ---
2
+ // src/pages/404.astro — the site's own "not found" page: the shell rewrites here.
3
+ import Layout from "../layouts/Layout.astro";
4
+ ---
5
+ <Layout head={{ title: "Not found", noindex: true, languages: {} }} locale="en">
6
+ <div class="container"><h1>Page not found</h1></div>
7
+ </Layout>
@@ -0,0 +1,26 @@
1
+ ---
2
+ // src/sections/favouriteArticles.astro — the view of the section named "favouriteArticles":
3
+ // articles an editor picked, resolved before render, so the view gets article fields, not ids.
4
+ import { smoodly, type Props as SmoodlyProps } from "smoodly";
5
+ import { Section } from "smoodly/astro/components";
6
+ import { href } from "smoodly/astro/runtime";
7
+ import { favouriteArticlesSchema } from "@/components/sections/FavouriteArticles.schema";
8
+ import { config } from "@/smoodly/config";
9
+ import ArticleCard from "../components/ArticleCard.astro";
10
+
11
+ type Props = SmoodlyProps<typeof favouriteArticlesSchema>;
12
+ const { heading, items, styles } = Astro.props;
13
+ // A section receives its schema's props and nothing of the page; the request's locale is the one page-level
14
+ // value the adapter leaves it, on Astro.locals (shell.ts sets it).
15
+ // Outside the shell (a page file of the project's own) it is unset: the default locale then.
16
+ const locale: string = Astro.locals.smoodly?.locale ?? config.locales.default;
17
+ const m = smoodly.marks(favouriteArticlesSchema);
18
+ ---
19
+ <Section styles={styles} class="favourites">
20
+ <div class="container">
21
+ <h2 class="h2" {...m.field("heading")}>{heading}</h2>
22
+ <div class="card-grid" {...m.field("items")}>
23
+ {(items ?? []).filter((a) => a !== null).map((a) => <ArticleCard article={a.fields} url={href(a.path!, locale)} locale={locale} />)}
24
+ </div>
25
+ </div>
26
+ </Section>
@@ -0,0 +1,37 @@
1
+ ---
2
+ // src/sections/footer.astro — the view of the section named "footer", a visual global item
3
+ // the layout renders with the contact object item, which is not a footer field.
4
+ import { safeHref, smoodly, type Props as SmoodlyProps, type Scope } from "smoodly";
5
+ import { Section } from "smoodly/astro/components";
6
+ import { footerSchema } from "@/components/sections/Footer.schema";
7
+
8
+ type Column = { title: string; links: { label: string; href: string }[] };
9
+ type Props = SmoodlyProps<typeof footerSchema> & { contact?: { email: string; phone?: string; address: string } | null };
10
+ const { tagline, company, legal, social, copyright, contact, styles } = Astro.props;
11
+ const m = smoodly.marks(footerSchema);
12
+ const columns: [Column | undefined, Scope<Column>][] = [[company, m.object("company")], [legal, m.object("legal")], [social, m.object("social")]];
13
+ ---
14
+ <Section as="footer" styles={styles} class="site-footer">
15
+ <div class="container site-footer__grid">
16
+ <div class="site-footer__about">
17
+ <p class="site-footer__tagline" {...m.field("tagline")}>{tagline}</p>
18
+ {contact && (
19
+ <address class="site-footer__contact">
20
+ <a href={`mailto:${contact.email}`}>{contact.email}</a>
21
+ {contact.phone && <span>{contact.phone}</span>}
22
+ <span>{contact.address}</span>
23
+ </address>
24
+ )}
25
+ </div>
26
+ {columns.map(([column, scope]) => column && (
27
+ <div class="site-footer__column" {...scope.attrs}>
28
+ <span class="site-footer__title" {...scope.field("title")}>{column.title}</span>
29
+ {(column.links ?? []).map((l, i) => {
30
+ const link = scope.item("links", i);
31
+ return <a href={safeHref(l.href)} {...link.attrs} {...link.field("label")}>{l.label}</a>;
32
+ })}
33
+ </div>
34
+ ))}
35
+ </div>
36
+ <div class="container site-footer__copyright" {...m.field("copyright")}>{copyright}</div>
37
+ </Section>
@@ -0,0 +1,27 @@
1
+ ---
2
+ // src/sections/header.astro — the view of the section named "header", a visual global item
3
+ // the layout renders with the locale links beside the schema's own props.
4
+ import { safeHref, smoodly, type Props as SmoodlyProps } from "smoodly";
5
+ import { Section } from "smoodly/astro/components";
6
+ import { headerSchema } from "@/components/sections/Header.schema";
7
+
8
+ type Props = SmoodlyProps<typeof headerSchema> & { home?: string; locales?: { code: string; url: string; current: boolean }[] };
9
+ const { brand, nav, home = "/", locales, styles } = Astro.props;
10
+ const m = smoodly.marks(headerSchema);
11
+ ---
12
+ <Section as="header" styles={styles} class="site-header">
13
+ <div class="container site-header__bar">
14
+ <a class="site-header__brand" href={home} {...m.field("brand")}>{brand}</a>
15
+ <nav class="site-header__nav">
16
+ {(nav ?? []).map((item, i) => {
17
+ const link = m.item("nav", i);
18
+ return <a href={safeHref(item.href)} {...link.attrs} {...link.field("label")}>{item.label}</a>;
19
+ })}
20
+ </nav>
21
+ {locales && locales.length > 1 && (
22
+ <nav class="site-header__locales" aria-label="Language">
23
+ {locales.map((l) => <a href={l.url} aria-current={l.current ? "page" : undefined}>{l.code.toUpperCase()}</a>)}
24
+ </nav>
25
+ )}
26
+ </div>
27
+ </Section>
@@ -0,0 +1,28 @@
1
+ ---
2
+ // src/sections/hero.astro — the view of the section named "hero"
3
+ import { safeHref, smoodly, type Props as SmoodlyProps } from "smoodly";
4
+ import { Image, Section } from "smoodly/astro/components";
5
+ import { heroSchema } from "@/components/sections/Hero.schema";
6
+
7
+ type Props = SmoodlyProps<typeof heroSchema>;
8
+ const { kicker, heading, text, buttons, image, styles } = Astro.props;
9
+ const m = smoodly.marks(heroSchema);
10
+ ---
11
+ <Section styles={styles} class="hero">
12
+ <div class="container hero__grid">
13
+ <div class="hero__copy">
14
+ {kicker && <p class="kicker" {...m.field("kicker")}>{kicker}</p>}
15
+ <h1 class="hero__heading" {...m.field("heading")}>{heading}</h1>
16
+ <p class="hero__text" {...m.field("text")}>{text}</p>
17
+ {buttons?.length > 0 && (
18
+ <div class="hero__buttons">
19
+ {buttons.map((b, i) => {
20
+ const button = m.item("buttons", i);
21
+ return <a class={i === 0 ? "button" : "button button--outline"} href={safeHref(b.href)} {...button.attrs} {...button.field("label")}>{b.label}</a>;
22
+ })}
23
+ </div>
24
+ )}
25
+ </div>
26
+ {image && <Image image={image} class="hero__image" sizes="(min-width: 900px) 40vw, 100vw" priority {...m.field("image")} />}
27
+ </div>
28
+ </Section>