@acms-subir/core 0.3.3-next.20260929113209 → 0.3.4

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 (289) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/cli/common.mjs +2 -2
  3. package/cli/legacy.mjs +1 -1
  4. package/cli/main.mjs +82 -8
  5. package/cli/mobile-doctor.mjs +430 -0
  6. package/cli/mobile.mjs +315 -0
  7. package/cli/native-sections.mjs +168 -0
  8. package/cli/preview.mjs +8 -4
  9. package/cli/sections.mjs +188 -0
  10. package/cli/themes.mjs +52 -8
  11. package/docs/mobile.md +527 -0
  12. package/docs/sections.md +203 -0
  13. package/docs/theme-authoring.md +19 -3
  14. package/integration.mjs +76 -5
  15. package/mobile-template/client/AGENTS.md +97 -0
  16. package/mobile-template/client/README.md +65 -0
  17. package/mobile-template/client/app/_layout.tsx +56 -0
  18. package/mobile-template/client/app/index.tsx +21 -0
  19. package/mobile-template/client/app.json +21 -0
  20. package/mobile-template/client/components/LanguageSwitch.tsx +31 -0
  21. package/mobile-template/client/eas.json +21 -0
  22. package/mobile-template/client/env.example +3 -0
  23. package/mobile-template/client/gitignore +42 -0
  24. package/mobile-template/client/package.json +38 -0
  25. package/mobile-template/client/sections/index.ts +11 -0
  26. package/mobile-template/client/tsconfig.json +7 -0
  27. package/mobile-template/editor/AGENTS.md +66 -0
  28. package/mobile-template/editor/README.md +73 -0
  29. package/mobile-template/editor/app/_layout.tsx +76 -0
  30. package/mobile-template/editor/app/auth.tsx +13 -0
  31. package/mobile-template/editor/app/index.tsx +55 -0
  32. package/mobile-template/editor/app.json +21 -0
  33. package/mobile-template/editor/components/Screen.tsx +51 -0
  34. package/mobile-template/editor/components/SignIn.tsx +36 -0
  35. package/mobile-template/editor/components/TextTabs.tsx +27 -0
  36. package/mobile-template/editor/eas.json +21 -0
  37. package/mobile-template/editor/env.example +5 -0
  38. package/mobile-template/editor/gitignore +42 -0
  39. package/mobile-template/editor/lib/appearance.tsx +12 -0
  40. package/mobile-template/editor/lib/config.ts +17 -0
  41. package/mobile-template/editor/lib/format.ts +24 -0
  42. package/mobile-template/editor/lib/tools.ts +16 -0
  43. package/mobile-template/editor/package.json +40 -0
  44. package/mobile-template/editor/tools/README.md +2 -0
  45. package/mobile-template/editor/tsconfig.json +7 -0
  46. package/mobile-template/recipes/booking/files/app/book/[resource].tsx +57 -0
  47. package/mobile-template/recipes/booking/files/app/book/details.tsx +105 -0
  48. package/mobile-template/recipes/booking/files/app/book/index.tsx +38 -0
  49. package/mobile-template/recipes/booking/recipe.json +17 -0
  50. package/mobile-template/recipes/editor-bookings/files/app/bookings/index.tsx +113 -0
  51. package/mobile-template/recipes/editor-bookings/files/tools/bookings.ts +1 -0
  52. package/mobile-template/recipes/editor-bookings/recipe.json +8 -0
  53. package/mobile-template/recipes/editor-content/files/app/content/[kind]/[id].tsx +13 -0
  54. package/mobile-template/recipes/editor-content/files/app/content/index.tsx +76 -0
  55. package/mobile-template/recipes/editor-content/files/components/DocumentDetail.tsx +55 -0
  56. package/mobile-template/recipes/editor-content/files/tools/content.ts +1 -0
  57. package/mobile-template/recipes/editor-content/recipe.json +8 -0
  58. package/mobile-template/recipes/editor-media/files/app/media/index.tsx +73 -0
  59. package/mobile-template/recipes/editor-media/files/tools/media.ts +1 -0
  60. package/mobile-template/recipes/editor-media/recipe.json +8 -0
  61. package/mobile-template/recipes/editor-sections/files/app/sections/[pageId]/[sectionId].tsx +12 -0
  62. package/mobile-template/recipes/editor-sections/files/app/sections/index.tsx +48 -0
  63. package/mobile-template/recipes/editor-sections/files/components/SectionEditorPane.tsx +77 -0
  64. package/mobile-template/recipes/editor-sections/files/tools/sections.ts +1 -0
  65. package/mobile-template/recipes/editor-sections/recipe.json +8 -0
  66. package/mobile-template/recipes/editor-translations/files/app/translations/index.tsx +63 -0
  67. package/mobile-template/recipes/editor-translations/files/app/translations/review.tsx +14 -0
  68. package/mobile-template/recipes/editor-translations/files/components/ReviewPane.tsx +72 -0
  69. package/mobile-template/recipes/editor-translations/files/tools/translations.ts +1 -0
  70. package/mobile-template/recipes/editor-translations/recipe.json +8 -0
  71. package/mobile-template/recipes/editor-writer/files/app/write/[kind]/[id].tsx +132 -0
  72. package/mobile-template/recipes/editor-writer/files/app/write/new.tsx +61 -0
  73. package/mobile-template/recipes/editor-writer/files/tools/writer.ts +1 -0
  74. package/mobile-template/recipes/editor-writer/recipe.json +8 -0
  75. package/mobile-template/recipes/events/files/app/events/[slug].tsx +41 -0
  76. package/mobile-template/recipes/events/files/app/events/index.tsx +50 -0
  77. package/mobile-template/recipes/events/files/lib/addToCalendar.ts +27 -0
  78. package/mobile-template/recipes/events/recipe.json +15 -0
  79. package/mobile-template/recipes/news/files/app/news/[slug].tsx +25 -0
  80. package/mobile-template/recipes/news/files/app/news/index.tsx +50 -0
  81. package/mobile-template/recipes/news/recipe.json +13 -0
  82. package/mobile-template/recipes/push/files/app/notifications.tsx +64 -0
  83. package/mobile-template/recipes/push/files/components/PushSetup.tsx +15 -0
  84. package/mobile-template/recipes/push/files/lib/push.ts +15 -0
  85. package/mobile-template/recipes/push/recipe.json +20 -0
  86. package/package.json +17 -13
  87. package/section-library/README.md +16 -0
  88. package/section-library/contact/Contact.astro +49 -0
  89. package/section-library/contact/section.json +106 -0
  90. package/section-library/cta-band/CtaBand.astro +28 -0
  91. package/section-library/cta-band/section.json +51 -0
  92. package/section-library/feature-grid/FeatureGrid.astro +54 -0
  93. package/section-library/feature-grid/section.json +107 -0
  94. package/section-library/gallery/Gallery.astro +29 -0
  95. package/section-library/gallery/section.json +51 -0
  96. package/section-library/hero-banner/HeroBanner.astro +42 -0
  97. package/section-library/hero-banner/section.json +81 -0
  98. package/section-library/latest-posts/LatestPosts.astro +22 -0
  99. package/section-library/latest-posts/section.json +37 -0
  100. package/section-library/pricing/Pricing.astro +40 -0
  101. package/section-library/pricing/section.json +117 -0
  102. package/section-library/questions/Questions.astro +38 -0
  103. package/section-library/questions/section.json +69 -0
  104. package/section-library/service-times/ServiceTimes.astro +42 -0
  105. package/section-library/service-times/section.json +127 -0
  106. package/section-library/team/Team.astro +45 -0
  107. package/section-library/team/section.json +88 -0
  108. package/section-library/upcoming-events/UpcomingEvents.astro +54 -0
  109. package/section-library/upcoming-events/section.json +51 -0
  110. package/src/actions/index.ts +205 -174
  111. package/src/components/AiSettings.tsx +45 -5
  112. package/src/components/DesignManager.tsx +112 -16
  113. package/src/components/MobileApp.tsx +392 -0
  114. package/src/components/ThemeSections.astro +40 -21
  115. package/src/components/ThemeSectionsEditor.tsx +757 -229
  116. package/src/components/booking/BookingFlow.astro +6 -3
  117. package/src/components/calendar/BookingsHead.astro +51 -0
  118. package/src/components/calendar/CalendarBoard.tsx +618 -0
  119. package/src/components/calendar/MiniMonth.tsx +54 -0
  120. package/src/components/calendar/NewEventDialog.tsx +44 -0
  121. package/src/components/calendar/QuickAdd.tsx +263 -0
  122. package/src/components/calendar/RoomHolds.tsx +203 -0
  123. package/src/components/calendar/WhereItLands.tsx +51 -0
  124. package/src/components/editor/EventPanel.tsx +23 -2
  125. package/src/components/editor/PostEditor.tsx +206 -14
  126. package/src/components/import/ImportExport.tsx +4 -1
  127. package/src/components/import/runner.ts +1 -0
  128. package/src/components/sections/SectionFields.tsx +560 -0
  129. package/src/components/sections/SectionPicker.tsx +120 -0
  130. package/src/components/sections/TranslationPanel.tsx +143 -0
  131. package/src/components/sections/checks.ts +64 -0
  132. package/src/layouts/Admin.astro +11 -7
  133. package/src/layouts/Site.astro +136 -21
  134. package/src/lib/agent-discovery.ts +11 -7
  135. package/src/lib/agent.ts +35 -2
  136. package/src/lib/ai-schema.ts +5 -0
  137. package/src/lib/ai.ts +5 -0
  138. package/src/lib/api.ts +12 -0
  139. package/src/lib/backup/content.ts +6 -1
  140. package/src/lib/booking/agent.ts +3 -2
  141. package/src/lib/booking/approval.ts +70 -0
  142. package/src/lib/booking/holds.ts +120 -0
  143. package/src/lib/booking/service.ts +37 -3
  144. package/src/lib/calendar/agent-schema.ts +9 -1
  145. package/src/lib/calendar/agent.ts +16 -1
  146. package/src/lib/calendar/board.ts +171 -0
  147. package/src/lib/calendar/config.ts +3 -0
  148. package/src/lib/calendar/grid.ts +169 -0
  149. package/src/lib/calendar/migration.ts +16 -0
  150. package/src/lib/calendar/render.ts +9 -7
  151. package/src/lib/calendar/schedule.ts +166 -0
  152. package/src/lib/content-service.ts +203 -0
  153. package/src/lib/design-service.ts +153 -5
  154. package/src/lib/drafts.ts +3 -3
  155. package/src/lib/editor/auth.ts +264 -0
  156. package/src/lib/editor/bookings.ts +60 -0
  157. package/src/lib/editor/config.ts +25 -0
  158. package/src/lib/editor/documents.ts +227 -0
  159. package/src/lib/editor/http.ts +48 -0
  160. package/src/lib/editor/me.ts +30 -0
  161. package/src/lib/editor/migration.ts +57 -0
  162. package/src/lib/editor/next.ts +11 -0
  163. package/src/lib/editor/openapi.ts +124 -0
  164. package/src/lib/editor/schemas.ts +91 -0
  165. package/src/lib/editor/sections.ts +96 -0
  166. package/src/lib/editor/store.ts +184 -0
  167. package/src/lib/editor/token-route.ts +74 -0
  168. package/src/lib/editor/translations.ts +163 -0
  169. package/src/lib/email/accounts.ts +2 -1
  170. package/src/lib/email/types.ts +2 -1
  171. package/src/lib/home-source.ts +34 -0
  172. package/src/lib/i18n.ts +34 -0
  173. package/src/lib/import/apply.ts +31 -2
  174. package/src/lib/import/bundle.ts +4 -0
  175. package/src/lib/import/export.ts +1 -0
  176. package/src/lib/import/ghost.ts +1 -0
  177. package/src/lib/media/upload.ts +83 -0
  178. package/src/lib/mobile/associations.ts +25 -0
  179. package/src/lib/mobile/calendar.ts +26 -14
  180. package/src/lib/mobile/config.mjs +59 -0
  181. package/src/lib/mobile/content.ts +18 -13
  182. package/src/lib/mobile/embed.ts +83 -0
  183. package/src/lib/mobile/http.ts +14 -6
  184. package/src/lib/mobile/locale.ts +18 -0
  185. package/src/lib/mobile/openapi.ts +111 -0
  186. package/src/lib/mobile/pages.ts +71 -0
  187. package/src/lib/mobile/route.ts +3 -3
  188. package/src/lib/mobile/schemas.ts +60 -0
  189. package/src/lib/mobile/site.ts +41 -0
  190. package/src/lib/push/api.ts +158 -0
  191. package/src/lib/push/config.ts +20 -0
  192. package/src/lib/push/expo.ts +76 -0
  193. package/src/lib/push/migration.ts +60 -0
  194. package/src/lib/push/service.ts +302 -0
  195. package/src/lib/push/store.ts +144 -0
  196. package/src/lib/push/types.ts +43 -0
  197. package/src/lib/repo/migrations.ts +14 -0
  198. package/src/lib/repo/sql.ts +65 -20
  199. package/src/lib/repo/types.ts +10 -4
  200. package/src/lib/roles.ts +5 -0
  201. package/src/lib/scheduler.ts +6 -1
  202. package/src/lib/schema.ts +76 -1
  203. package/src/lib/section-ai.ts +55 -0
  204. package/src/lib/section-targets.ts +62 -0
  205. package/src/lib/sections-agent.ts +146 -0
  206. package/src/lib/sections.ts +242 -0
  207. package/src/lib/site-chrome.ts +34 -0
  208. package/src/lib/theme-assets.ts +16 -0
  209. package/src/lib/theme.ts +33 -4
  210. package/src/lib/translate.ts +97 -0
  211. package/src/lib/translations.ts +39 -0
  212. package/src/middleware.ts +38 -2
  213. package/src/pages/.well-known/apple-app-site-association.ts +6 -0
  214. package/src/pages/.well-known/assetlinks.json.ts +6 -0
  215. package/src/pages/[...slug].astro +8 -3
  216. package/src/pages/admin/app-auth.astro +159 -0
  217. package/src/pages/admin/c/[collection]/[id].astro +9 -0
  218. package/src/pages/admin/calendar/bookings.astro +32 -26
  219. package/src/pages/admin/calendar/calendars.astro +4 -263
  220. package/src/pages/admin/calendar/events/[id].astro +9 -0
  221. package/src/pages/admin/calendar/events/index.astro +41 -83
  222. package/src/pages/admin/calendar/index.astro +43 -0
  223. package/src/pages/admin/calendar/resources/[id].astro +27 -5
  224. package/src/pages/admin/calendar/resources/index.astro +16 -17
  225. package/src/pages/admin/calendar/resources/link.astro +52 -12
  226. package/src/pages/admin/calendar/settings.astro +458 -0
  227. package/src/pages/admin/integrations.astro +5 -11
  228. package/src/pages/admin/login.astro +6 -2
  229. package/src/pages/admin/logout.ts +5 -2
  230. package/src/pages/admin/mobile.astro +18 -0
  231. package/src/pages/admin/pages/[id].astro +62 -0
  232. package/src/pages/admin/section-preview.astro +61 -0
  233. package/src/pages/admin/users.astro +107 -11
  234. package/src/pages/api/agent/docs.ts +6 -2
  235. package/src/pages/api/agent/index.ts +1 -1
  236. package/src/pages/api/ai/settings.ts +7 -4
  237. package/src/pages/api/design/ai.ts +15 -0
  238. package/src/pages/api/design/index.ts +12 -42
  239. package/src/pages/api/design/translate.ts +15 -0
  240. package/src/pages/api/live/thumb.ts +1 -1
  241. package/src/pages/api/mcp.ts +2 -2
  242. package/src/pages/api/media/upload.ts +10 -78
  243. package/src/pages/api/push/admin.ts +27 -0
  244. package/src/pages/api/v1/admin/bookings/[id]/[action].ts +14 -0
  245. package/src/pages/api/v1/admin/bookings/index.ts +6 -0
  246. package/src/pages/api/v1/admin/documents/[kind]/[id]/[action].ts +17 -0
  247. package/src/pages/api/v1/admin/documents/[kind]/[id]/index.ts +20 -0
  248. package/src/pages/api/v1/admin/documents/[kind]/[id]/translations/[locale]/[action].ts +26 -0
  249. package/src/pages/api/v1/admin/documents/[kind]/[id]/translations/[locale]/index.ts +18 -0
  250. package/src/pages/api/v1/admin/documents/index.ts +13 -0
  251. package/src/pages/api/v1/admin/me.ts +6 -0
  252. package/src/pages/api/v1/admin/media/index.ts +32 -0
  253. package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/index.ts +14 -0
  254. package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/publish.ts +14 -0
  255. package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/translations/[locale]/[action].ts +22 -0
  256. package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/translations/[locale]/index.ts +14 -0
  257. package/src/pages/api/v1/admin/sections/index.ts +6 -0
  258. package/src/pages/api/v1/auth/revoke.ts +5 -0
  259. package/src/pages/api/v1/auth/token.ts +5 -0
  260. package/src/pages/api/v1/devices/[token].ts +20 -0
  261. package/src/pages/api/v1/devices/index.ts +17 -0
  262. package/src/pages/api/v1/openapi.json.ts +7 -0
  263. package/src/pages/api/v1/pages/[...slug].ts +7 -0
  264. package/src/pages/api/v1/pages/home.ts +7 -0
  265. package/src/pages/api/v1/push/topics.ts +7 -0
  266. package/src/pages/api/v1/site.ts +7 -0
  267. package/src/pages/api/v1/theme.ts +7 -0
  268. package/src/pages/blog/[slug].astro +8 -2
  269. package/src/pages/blog/index.astro +11 -4
  270. package/src/pages/book/[resource].ics.ts +7 -1
  271. package/src/pages/embed/section/[...path].astro +42 -0
  272. package/src/pages/events/[slug].astro +7 -2
  273. package/src/pages/index.astro +11 -5
  274. package/src/pages/sitemap.xml.ts +8 -0
  275. package/src/styles/admin.css +117 -0
  276. package/src/styles/cards.css +5 -1
  277. package/src/styles/editor.css +57 -0
  278. package/src/styles/mobile-app.css +151 -0
  279. package/src/styles/schedule.css +325 -0
  280. package/src/styles/sections.css +21 -12
  281. package/src/styles/theme-editor.css +255 -0
  282. package/src/theme-sdk/contract.d.mts +2193 -89
  283. package/src/theme-sdk/contract.mjs +77 -20
  284. package/src/theme-sdk/fields.d.mts +88 -0
  285. package/src/theme-sdk/fields.mjs +330 -0
  286. package/src/theme-sdk/index.ts +14 -2
  287. package/src/theme-sdk/tokens.d.mts +11 -0
  288. package/src/virtual.d.ts +4 -0
  289. package/src/pages/admin/calendar/index.ts +0 -4
@@ -0,0 +1,117 @@
1
+ {
2
+ "type": "pricing",
3
+ "definition": {
4
+ "name": "Pricing",
5
+ "version": 1,
6
+ "component": "sections/Pricing.astro",
7
+ "description": "Plans or tiers side by side, one can be highlighted.",
8
+ "variants": [
9
+ "default"
10
+ ],
11
+ "fields": {
12
+ "heading": {
13
+ "label": "Heading",
14
+ "type": "text",
15
+ "default": "Plans"
16
+ },
17
+ "intro": {
18
+ "label": "Intro",
19
+ "type": "textarea",
20
+ "default": ""
21
+ },
22
+ "plans": {
23
+ "label": "Plans",
24
+ "type": "list",
25
+ "itemLabel": "name",
26
+ "max": 6,
27
+ "fields": {
28
+ "name": {
29
+ "label": "Name",
30
+ "type": "text"
31
+ },
32
+ "price": {
33
+ "label": "Price",
34
+ "type": "text",
35
+ "placeholder": "$20",
36
+ "translatable": false
37
+ },
38
+ "period": {
39
+ "label": "Per",
40
+ "type": "text",
41
+ "placeholder": "month"
42
+ },
43
+ "description": {
44
+ "label": "Description",
45
+ "type": "textarea"
46
+ },
47
+ "features": {
48
+ "label": "Included (one per line)",
49
+ "type": "textarea"
50
+ },
51
+ "cta": {
52
+ "label": "Button",
53
+ "type": "link"
54
+ },
55
+ "featured": {
56
+ "label": "Highlight this plan",
57
+ "type": "toggle"
58
+ }
59
+ }
60
+ }
61
+ },
62
+ "category": "Info",
63
+ "example": {
64
+ "plans": [
65
+ {
66
+ "name": "Drop-in",
67
+ "price": "$15",
68
+ "period": "class",
69
+ "features": "Any class\nMat included",
70
+ "cta": {
71
+ "label": "Book a class",
72
+ "href": "/book"
73
+ }
74
+ },
75
+ {
76
+ "name": "Monthly",
77
+ "price": "$90",
78
+ "period": "month",
79
+ "features": "Unlimited classes\nFree workshop",
80
+ "cta": {
81
+ "label": "Join",
82
+ "href": "/join"
83
+ },
84
+ "featured": true
85
+ }
86
+ ]
87
+ }
88
+ },
89
+ "example": {
90
+ "variant": "default",
91
+ "data": {
92
+ "plans": [
93
+ {
94
+ "name": "Drop-in",
95
+ "price": "$15",
96
+ "period": "class",
97
+ "features": "Any class\nMat included",
98
+ "cta": {
99
+ "label": "Book a class",
100
+ "href": "/book"
101
+ }
102
+ },
103
+ {
104
+ "name": "Monthly",
105
+ "price": "$90",
106
+ "period": "month",
107
+ "features": "Unlimited classes\nFree workshop",
108
+ "cta": {
109
+ "label": "Join",
110
+ "href": "/join"
111
+ },
112
+ "featured": true
113
+ }
114
+ ]
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,38 @@
1
+ ---
2
+ import type { SectionProps } from '@acms-subir/core/theme';
3
+ interface Q { question: string; answer: string }
4
+ interface Data { heading: string; intro: string; questions: Q[] }
5
+ const { data, variant } = Astro.props as SectionProps<Data>;
6
+ const questions = data.questions.filter((q) => q.question);
7
+ ---
8
+ <div class:list={['faq', `is-${variant}`]}>
9
+ <header class="faq-head">
10
+ <h2>{data.heading}</h2>
11
+ {data.intro && <p>{data.intro}</p>}
12
+ </header>
13
+ <div class="faq-list">
14
+ {questions.map((q) => (
15
+ <details class="faq-item">
16
+ <summary>{q.question}</summary>
17
+ <!-- answer is sanitized rich text from the core -->
18
+ <div class="faq-answer" set:html={q.answer} />
19
+ </details>
20
+ ))}
21
+ </div>
22
+ </div>
23
+ <style>
24
+ .faq-head h2 { margin: 0; }
25
+ .faq-head p { margin: 0.5rem 0 0; color: var(--muted); max-width: 60ch; }
26
+ .faq-list { margin-top: 1.5rem; border-top: 1px solid var(--line); }
27
+ .is-two-column { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); gap: 2rem; }
28
+ .is-two-column .faq-list { margin-top: 0; }
29
+ .faq-item { border-bottom: 1px solid var(--line); }
30
+ summary { display: flex; justify-content: space-between; gap: 1rem; padding: 1rem 0; cursor: pointer; font-weight: 600; font-size: 1.05rem; list-style: none; }
31
+ summary::-webkit-details-marker { display: none; }
32
+ summary::after { content: '+'; color: var(--muted); font-weight: 400; font-size: 1.3rem; line-height: 1; transition: transform 0.15s; }
33
+ details[open] summary::after { transform: rotate(45deg); }
34
+ .faq-answer { padding: 0 0 1rem; color: var(--muted); }
35
+ .faq-answer :global(p) { margin: 0 0 0.6rem; }
36
+ .faq-answer :global(a) { color: var(--accent-text, var(--accent)); }
37
+ @media (max-width: 760px) { .is-two-column { grid-template-columns: 1fr; gap: 1rem; } }
38
+ </style>
@@ -0,0 +1,69 @@
1
+ {
2
+ "type": "questions",
3
+ "definition": {
4
+ "name": "Questions",
5
+ "version": 1,
6
+ "component": "sections/Questions.astro",
7
+ "description": "Questions and answers that open on click. Answers can have links and lists.",
8
+ "variants": [
9
+ "accordion",
10
+ "two-column"
11
+ ],
12
+ "fields": {
13
+ "heading": {
14
+ "label": "Heading",
15
+ "type": "text",
16
+ "default": "Questions"
17
+ },
18
+ "intro": {
19
+ "label": "Intro",
20
+ "type": "textarea",
21
+ "default": ""
22
+ },
23
+ "questions": {
24
+ "label": "Questions",
25
+ "type": "list",
26
+ "itemLabel": "question",
27
+ "max": 40,
28
+ "fields": {
29
+ "question": {
30
+ "label": "Question",
31
+ "type": "text"
32
+ },
33
+ "answer": {
34
+ "label": "Answer",
35
+ "type": "richtext"
36
+ }
37
+ }
38
+ }
39
+ },
40
+ "category": "Info",
41
+ "example": {
42
+ "questions": [
43
+ {
44
+ "question": "Where do I park?",
45
+ "answer": "<p>Free parking behind the building on Elm St.</p>"
46
+ },
47
+ {
48
+ "question": "What about my kids?",
49
+ "answer": "<p>Kids’ church runs during the 10:00 service for ages 3–11.</p>"
50
+ }
51
+ ]
52
+ }
53
+ },
54
+ "example": {
55
+ "variant": "accordion",
56
+ "data": {
57
+ "questions": [
58
+ {
59
+ "question": "Where do I park?",
60
+ "answer": "<p>Free parking behind the building on Elm St.</p>"
61
+ },
62
+ {
63
+ "question": "What about my kids?",
64
+ "answer": "<p>Kids’ church runs during the 10:00 service for ages 3–11.</p>"
65
+ }
66
+ ]
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,42 @@
1
+ ---
2
+ import type { SectionProps } from '@acms-subir/core/theme';
3
+ import { formatTime } from '@acms-subir/core/theme';
4
+ interface Service { title: string; day: string; time: string; note: string; streamed: boolean }
5
+ interface Data { heading: string; intro: string; services: Service[]; footnote: string }
6
+ const { data, variant, settings } = Astro.props as SectionProps<Data>;
7
+ const services = data.services.filter((s) => s.title);
8
+ const locale = settings.locale || 'en-US';
9
+ ---
10
+ <div class:list={['times', `is-${variant}`]}>
11
+ <header class="times-head">
12
+ <h2>{data.heading}</h2>
13
+ {data.intro && <p>{data.intro}</p>}
14
+ </header>
15
+ <ul class="times-list">
16
+ {services.map((s) => (
17
+ <li class="times-item">
18
+ <span class="times-when"><span class="times-day">{s.day}</span> <span class="times-time">{formatTime(s.time, locale)}</span></span>
19
+ <b class="times-title">{s.title}</b>
20
+ {s.note && <span class="times-note">{s.note}</span>}
21
+ {s.streamed && <span class="times-live"><i aria-hidden="true"></i>Streamed online</span>}
22
+ </li>
23
+ ))}
24
+ </ul>
25
+ {data.footnote && <p class="times-foot">{data.footnote}</p>}
26
+ </div>
27
+ <style>
28
+ .times-head h2 { margin: 0; }
29
+ .times-head p { margin: 0.5rem 0 0; color: var(--muted); max-width: 60ch; }
30
+ .times-list { list-style: none; margin: 1.5rem 0 0; padding: 0; display: grid; gap: 0.9rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }
31
+ .times-item { display: flex; flex-direction: column; gap: 0.3rem; padding: 1.1rem 1.2rem; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); }
32
+ .times-when { font-size: 0.85rem; font-weight: 600; color: var(--accent-text, var(--accent)); letter-spacing: 0.02em; }
33
+ .times-title { font-family: var(--font-heading); font-size: 1.2rem; line-height: 1.25; }
34
+ .times-note { color: var(--muted); font-size: 0.95rem; }
35
+ .times-live { display: inline-flex; align-items: center; gap: 0.4rem; margin-top: 0.25rem; font-size: 0.8rem; color: var(--muted); }
36
+ .times-live i { width: 7px; height: 7px; border-radius: 2px; background: var(--danger, #d33); }
37
+ .times.is-strip .times-list { grid-template-columns: 1fr; gap: 0; border-top: 1px solid var(--line); }
38
+ .times.is-strip .times-item { flex-direction: row; flex-wrap: wrap; align-items: baseline; column-gap: 1rem; background: none; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; padding: 0.9rem 0; }
39
+ .times.is-strip .times-when { min-width: 11rem; }
40
+ .times.is-strip .times-title { font-size: 1.05rem; }
41
+ .times-foot { margin: 1rem 0 0; font-size: 0.9rem; color: var(--muted); }
42
+ </style>
@@ -0,0 +1,127 @@
1
+ {
2
+ "type": "service-times",
3
+ "definition": {
4
+ "name": "Service times",
5
+ "version": 1,
6
+ "component": "sections/ServiceTimes.astro",
7
+ "description": "Weekly gatherings with day, time and a short note. Edit the list any time.",
8
+ "variants": [
9
+ "cards",
10
+ "strip"
11
+ ],
12
+ "fields": {
13
+ "heading": {
14
+ "label": "Heading",
15
+ "type": "text",
16
+ "default": "Join us this week"
17
+ },
18
+ "intro": {
19
+ "label": "Intro",
20
+ "type": "textarea",
21
+ "default": ""
22
+ },
23
+ "services": {
24
+ "label": "Services",
25
+ "type": "list",
26
+ "itemLabel": "title",
27
+ "max": 12,
28
+ "fields": {
29
+ "title": {
30
+ "label": "Name",
31
+ "type": "text"
32
+ },
33
+ "day": {
34
+ "label": "Day",
35
+ "type": "select",
36
+ "options": [
37
+ "Sunday",
38
+ "Monday",
39
+ "Tuesday",
40
+ "Wednesday",
41
+ "Thursday",
42
+ "Friday",
43
+ "Saturday",
44
+ "Daily",
45
+ "Weekdays"
46
+ ],
47
+ "default": "Sunday",
48
+ "translatable": true
49
+ },
50
+ "time": {
51
+ "label": "Time",
52
+ "type": "time",
53
+ "default": "10:00"
54
+ },
55
+ "note": {
56
+ "label": "Note",
57
+ "type": "text",
58
+ "placeholder": "Ages 12–18, pizza after"
59
+ },
60
+ "streamed": {
61
+ "label": "Streamed online",
62
+ "type": "toggle"
63
+ }
64
+ }
65
+ },
66
+ "footnote": {
67
+ "label": "Small print",
68
+ "type": "text",
69
+ "default": ""
70
+ }
71
+ },
72
+ "category": "Events & times",
73
+ "example": {
74
+ "services": [
75
+ {
76
+ "title": "Sunday Worship",
77
+ "day": "Sunday",
78
+ "time": "10:00",
79
+ "note": "Kids’ program included",
80
+ "streamed": true
81
+ },
82
+ {
83
+ "title": "Youth Night",
84
+ "day": "Wednesday",
85
+ "time": "19:00",
86
+ "note": "Ages 12–18, pizza after",
87
+ "streamed": false
88
+ },
89
+ {
90
+ "title": "Morning Prayer",
91
+ "day": "Friday",
92
+ "time": "07:00",
93
+ "note": "Chapel",
94
+ "streamed": false
95
+ }
96
+ ]
97
+ }
98
+ },
99
+ "example": {
100
+ "variant": "cards",
101
+ "data": {
102
+ "services": [
103
+ {
104
+ "title": "Sunday Worship",
105
+ "day": "Sunday",
106
+ "time": "10:00",
107
+ "note": "Kids’ program included",
108
+ "streamed": true
109
+ },
110
+ {
111
+ "title": "Youth Night",
112
+ "day": "Wednesday",
113
+ "time": "19:00",
114
+ "note": "Ages 12–18, pizza after",
115
+ "streamed": false
116
+ },
117
+ {
118
+ "title": "Morning Prayer",
119
+ "day": "Friday",
120
+ "time": "07:00",
121
+ "note": "Chapel",
122
+ "streamed": false
123
+ }
124
+ ]
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ import type { SectionProps } from '@acms-subir/core/theme';
3
+ interface Person { name: string; role: string; photo: string; photoFocus: string; bio: string; email: string }
4
+ interface Data { heading: string; intro: string; people: Person[] }
5
+ const { data, variant } = Astro.props as SectionProps<Data>;
6
+ const people = data.people.filter((p) => p.name);
7
+ const initials = (name: string) => name.split(/\s+/).map((w) => w[0]).slice(0, 2).join('').toUpperCase();
8
+ const validEmail = (e: string) => /^[^\s@<>"]+@[^\s@<>"]+\.[^\s@<>"]+$/.test(e.trim());
9
+ ---
10
+ <div class:list={['team', `is-${variant}`]}>
11
+ <header class="team-head">
12
+ <h2>{data.heading}</h2>
13
+ {data.intro && <p>{data.intro}</p>}
14
+ </header>
15
+ <ul class="team-list">
16
+ {people.map((p) => (
17
+ <li class="team-card">
18
+ {p.photo ? <img class="team-photo" src={p.photo} alt="" loading="lazy" style={`object-position:${p.photoFocus}`} /> : <span class="team-photo is-initials" aria-hidden="true">{initials(p.name)}</span>}
19
+ <div class="team-body">
20
+ <b class="team-name">{p.name}</b>
21
+ {p.role && <span class="team-role">{p.role}</span>}
22
+ {variant === 'grid' && p.bio && <p class="team-bio">{p.bio}</p>}
23
+ {p.email && validEmail(p.email) && <a class="team-email" href={`mailto:${p.email.trim()}`}>{p.email.trim()}</a>}
24
+ </div>
25
+ </li>
26
+ ))}
27
+ </ul>
28
+ </div>
29
+ <style>
30
+ .team-head h2 { margin: 0; }
31
+ .team-head p { margin: 0.5rem 0 0; color: var(--muted); max-width: 60ch; }
32
+ .team-list { list-style: none; margin: 1.5rem 0 0; padding: 0; display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr)); }
33
+ .team-card { display: flex; flex-direction: column; gap: 0.75rem; }
34
+ .team-photo { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--r); background: var(--soft); }
35
+ .team-photo.is-initials { display: grid; place-items: center; font-family: var(--font-heading); font-size: 2rem; color: var(--muted); }
36
+ .team-body { display: flex; flex-direction: column; gap: 0.15rem; }
37
+ .team-name { font-size: 1.05rem; }
38
+ .team-role { color: var(--muted); font-size: 0.92rem; }
39
+ .team-bio { margin: 0.4rem 0 0; font-size: 0.95rem; color: var(--muted); }
40
+ .team-email { margin-top: 0.3rem; font-size: 0.9rem; color: var(--accent-text, var(--accent)); overflow-wrap: anywhere; }
41
+ .is-compact .team-list { grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr)); gap: 1rem; }
42
+ .is-compact .team-card { flex-direction: row; align-items: center; }
43
+ .is-compact .team-photo { width: 3.5rem; flex-shrink: 0; border-radius: var(--r-sm); }
44
+ .is-compact .team-photo.is-initials { font-size: 1.1rem; }
45
+ </style>
@@ -0,0 +1,88 @@
1
+ {
2
+ "type": "team",
3
+ "definition": {
4
+ "name": "Team",
5
+ "version": 1,
6
+ "component": "sections/Team.astro",
7
+ "description": "People with photo, role and a short bio.",
8
+ "variants": [
9
+ "grid",
10
+ "compact"
11
+ ],
12
+ "fields": {
13
+ "heading": {
14
+ "label": "Heading",
15
+ "type": "text",
16
+ "default": "Meet the team"
17
+ },
18
+ "intro": {
19
+ "label": "Intro",
20
+ "type": "textarea",
21
+ "default": ""
22
+ },
23
+ "people": {
24
+ "label": "People",
25
+ "type": "list",
26
+ "itemLabel": "name",
27
+ "max": 40,
28
+ "fields": {
29
+ "name": {
30
+ "label": "Name",
31
+ "type": "text",
32
+ "translatable": false
33
+ },
34
+ "role": {
35
+ "label": "Role",
36
+ "type": "text"
37
+ },
38
+ "photo": {
39
+ "label": "Photo",
40
+ "type": "image"
41
+ },
42
+ "bio": {
43
+ "label": "Short bio",
44
+ "type": "textarea"
45
+ },
46
+ "email": {
47
+ "label": "Email",
48
+ "type": "text",
49
+ "placeholder": "name@example.org",
50
+ "translatable": false
51
+ }
52
+ }
53
+ }
54
+ },
55
+ "category": "People",
56
+ "example": {
57
+ "people": [
58
+ {
59
+ "name": "Ana Ruiz",
60
+ "role": "Lead pastor",
61
+ "bio": ""
62
+ },
63
+ {
64
+ "name": "Sam Okafor",
65
+ "role": "Youth director",
66
+ "bio": ""
67
+ }
68
+ ]
69
+ }
70
+ },
71
+ "example": {
72
+ "variant": "grid",
73
+ "data": {
74
+ "people": [
75
+ {
76
+ "name": "Ana Ruiz",
77
+ "role": "Lead pastor",
78
+ "bio": ""
79
+ },
80
+ {
81
+ "name": "Sam Okafor",
82
+ "role": "Youth director",
83
+ "bio": ""
84
+ }
85
+ ]
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,54 @@
1
+ ---
2
+ import type { SectionProps, EventItem, LinkValue } from '@acms-subir/core/theme';
3
+ interface Data { heading: string; intro: string; events: EventItem[]; more: LinkValue; empty: string }
4
+ const { data, variant } = Astro.props as SectionProps<Data>;
5
+ ---
6
+ <div class:list={['upev', `is-${variant}`]}>
7
+ <header class="upev-head">
8
+ <div>
9
+ <h2>{data.heading}</h2>
10
+ {data.intro && <p>{data.intro}</p>}
11
+ </div>
12
+ {data.more?.label && data.more.href && <a class="upev-more" href={data.more.href}>{data.more.label} →</a>}
13
+ </header>
14
+ {data.events.length === 0 ? <p class="upev-empty">{data.empty}</p> : (
15
+ <ol class="upev-list">
16
+ {data.events.map((e) => (
17
+ <li class:list={['upev-item', { 'is-cancelled': e.cancelled }]}>
18
+ {variant === 'cards' && e.image
19
+ ? <a class="upev-media" href={e.url} tabindex="-1" aria-hidden="true"><img src={e.image} alt="" loading="lazy" /></a>
20
+ : <span class="upev-date" aria-hidden="true"><b>{e.day}</b>{e.month}</span>}
21
+ <div class="upev-body">
22
+ <span class="upev-when">{e.cancelled ? 'Cancelled · ' : ''}{e.when}</span>
23
+ <h3 class="upev-title"><a href={e.url}>{e.title}</a></h3>
24
+ {e.location && <span class="upev-loc">{e.location}</span>}
25
+ </div>
26
+ </li>
27
+ ))}
28
+ </ol>
29
+ )}
30
+ </div>
31
+ <style>
32
+ .upev-head { display: flex; flex-wrap: wrap; gap: 0.5rem 2rem; justify-content: space-between; align-items: flex-end; }
33
+ .upev-head h2 { margin: 0; }
34
+ .upev-head p { margin: 0.5rem 0 0; color: var(--muted); max-width: 60ch; }
35
+ .upev-more { font-weight: 600; color: var(--accent-text, var(--accent)); text-decoration: none; white-space: nowrap; }
36
+ .upev-more:hover { text-decoration: underline; }
37
+ .upev-empty { margin: 1.25rem 0 0; color: var(--muted); }
38
+ .upev-list { list-style: none; margin: 1.5rem 0 0; padding: 0; display: grid; gap: 1rem; }
39
+ .is-cards .upev-list { grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr)); }
40
+ .upev-item { display: flex; gap: 1rem; align-items: flex-start; }
41
+ .is-cards .upev-item { flex-direction: column; padding: 1rem; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); }
42
+ .is-list .upev-item { padding-bottom: 1rem; border-bottom: 1px solid var(--line); }
43
+ .upev-media { display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: var(--r-sm); overflow: hidden; background: var(--soft); }
44
+ .upev-media img { width: 100%; height: 100%; object-fit: cover; }
45
+ .upev-date { flex-shrink: 0; display: grid; place-items: center; width: 3.4rem; padding: 0.45rem 0; border-radius: var(--r-sm); background: var(--ink); color: var(--bg); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.06em; line-height: 1.2; }
46
+ .upev-date b { font-size: 1.35rem; letter-spacing: 0; }
47
+ .upev-body { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
48
+ .upev-when { font-size: 0.85rem; color: var(--muted); }
49
+ .upev-title { margin: 0; font-size: 1.15rem; line-height: 1.3; }
50
+ .upev-title a { color: inherit; text-decoration: none; }
51
+ .upev-title a:hover { text-decoration: underline; }
52
+ .upev-loc { font-size: 0.9rem; color: var(--muted); }
53
+ .is-cancelled .upev-title { text-decoration: line-through; opacity: 0.7; }
54
+ </style>
@@ -0,0 +1,51 @@
1
+ {
2
+ "type": "upcoming-events",
3
+ "definition": {
4
+ "name": "Upcoming events",
5
+ "version": 1,
6
+ "component": "sections/UpcomingEvents.astro",
7
+ "description": "The next events from your calendar. Stays current on its own.",
8
+ "variants": [
9
+ "cards",
10
+ "list"
11
+ ],
12
+ "fields": {
13
+ "heading": {
14
+ "label": "Heading",
15
+ "type": "text",
16
+ "default": "What’s coming up"
17
+ },
18
+ "intro": {
19
+ "label": "Intro",
20
+ "type": "textarea",
21
+ "default": ""
22
+ },
23
+ "events": {
24
+ "label": "Events",
25
+ "type": "source",
26
+ "source": "events",
27
+ "default": {
28
+ "limit": 3
29
+ }
30
+ },
31
+ "more": {
32
+ "label": "“See all” link",
33
+ "type": "link",
34
+ "default": {
35
+ "label": "See the full calendar",
36
+ "href": "/calendar"
37
+ }
38
+ },
39
+ "empty": {
40
+ "label": "Text when nothing is scheduled",
41
+ "type": "text",
42
+ "default": "Nothing scheduled right now. Check back soon."
43
+ }
44
+ },
45
+ "category": "Events & times"
46
+ },
47
+ "example": {
48
+ "variant": "cards",
49
+ "data": {}
50
+ }
51
+ }