@morphika/andami 0.1.2

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 (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/admin/assets.ts +4 -0
  4. package/admin/database.ts +4 -0
  5. package/admin/index.ts +6 -0
  6. package/admin/login.ts +4 -0
  7. package/admin/navigation.ts +4 -0
  8. package/admin/pages-editor.ts +4 -0
  9. package/admin/pages.ts +4 -0
  10. package/admin/projects-editor.ts +4 -0
  11. package/admin/projects.ts +4 -0
  12. package/admin/settings.ts +4 -0
  13. package/admin/setup.ts +4 -0
  14. package/admin/storage.ts +4 -0
  15. package/admin/styles.ts +4 -0
  16. package/app/(site)/[slug]/loading.tsx +20 -0
  17. package/app/(site)/[slug]/page.tsx +83 -0
  18. package/app/(site)/error.tsx +32 -0
  19. package/app/(site)/layout.tsx +53 -0
  20. package/app/(site)/loading.tsx +20 -0
  21. package/app/(site)/not-found.tsx +41 -0
  22. package/app/(site)/page.tsx +43 -0
  23. package/app/(site)/preview/page.tsx +99 -0
  24. package/app/(site)/work/[slug]/loading.tsx +23 -0
  25. package/app/(site)/work/[slug]/page.tsx +84 -0
  26. package/app/admin/assets/page.tsx +573 -0
  27. package/app/admin/database/page.tsx +302 -0
  28. package/app/admin/error.tsx +53 -0
  29. package/app/admin/layout.tsx +273 -0
  30. package/app/admin/login/page.tsx +88 -0
  31. package/app/admin/navigation/page.tsx +157 -0
  32. package/app/admin/page.tsx +17 -0
  33. package/app/admin/pages/[slug]/page.tsx +849 -0
  34. package/app/admin/pages/page.tsx +588 -0
  35. package/app/admin/projects/[slug]/page.tsx +3 -0
  36. package/app/admin/projects/page.tsx +669 -0
  37. package/app/admin/settings/page.tsx +132 -0
  38. package/app/admin/setup/page.tsx +64 -0
  39. package/app/admin/storage/page.tsx +518 -0
  40. package/app/admin/styles/page.tsx +243 -0
  41. package/app/api/admin/assets/file/route.ts +81 -0
  42. package/app/api/admin/assets/health/route.ts +170 -0
  43. package/app/api/admin/assets/register/route.ts +163 -0
  44. package/app/api/admin/assets/registry/route.ts +98 -0
  45. package/app/api/admin/assets/relink/confirm/route.ts +242 -0
  46. package/app/api/admin/assets/relink/route.ts +202 -0
  47. package/app/api/admin/assets/scan/route.ts +271 -0
  48. package/app/api/admin/auth/route.ts +160 -0
  49. package/app/api/admin/custom-sections/[slug]/route.ts +159 -0
  50. package/app/api/admin/custom-sections/route.ts +127 -0
  51. package/app/api/admin/database/route.ts +53 -0
  52. package/app/api/admin/pages/[slug]/duplicate/route.ts +91 -0
  53. package/app/api/admin/pages/[slug]/route.ts +617 -0
  54. package/app/api/admin/pages/[slug]/set-home/route.ts +76 -0
  55. package/app/api/admin/pages/route.ts +129 -0
  56. package/app/api/admin/preview/route.ts +53 -0
  57. package/app/api/admin/r2/connect/route.ts +181 -0
  58. package/app/api/admin/r2/delete/route.ts +198 -0
  59. package/app/api/admin/r2/disconnect/route.ts +42 -0
  60. package/app/api/admin/r2/rename/route.ts +265 -0
  61. package/app/api/admin/r2/status/route.ts +106 -0
  62. package/app/api/admin/r2/upload-url/route.ts +148 -0
  63. package/app/api/admin/revalidate/route.ts +55 -0
  64. package/app/api/admin/settings/route.ts +279 -0
  65. package/app/api/admin/setup/complete/route.ts +51 -0
  66. package/app/api/admin/setup/route.ts +118 -0
  67. package/app/api/admin/storage/switch/route.ts +117 -0
  68. package/app/api/admin/styles/fonts/route.ts +97 -0
  69. package/app/api/admin/styles/route.ts +304 -0
  70. package/app/api/assets/[...path]/route.ts +98 -0
  71. package/app/api/custom-sections/[id]/route.ts +43 -0
  72. package/app/api/draft-mode/disable/route.ts +10 -0
  73. package/app/api/draft-mode/enable/route.ts +26 -0
  74. package/app/api/projects/route.ts +42 -0
  75. package/app/api/styles/route.ts +88 -0
  76. package/app/favicon.ico +0 -0
  77. package/app/globals.css +7 -0
  78. package/app/layout.tsx +53 -0
  79. package/app/robots.ts +17 -0
  80. package/app/sitemap.ts +48 -0
  81. package/app/studio/[[...index]]/page.tsx +8 -0
  82. package/components/admin/MetadataEditor.tsx +173 -0
  83. package/components/admin/PublishToggle.tsx +130 -0
  84. package/components/admin/icons.tsx +40 -0
  85. package/components/admin/nav-builder/NavBuilder.tsx +182 -0
  86. package/components/admin/nav-builder/NavBuilderGrid.tsx +326 -0
  87. package/components/admin/nav-builder/NavGeneralSettings.tsx +275 -0
  88. package/components/admin/nav-builder/NavGridCell.tsx +48 -0
  89. package/components/admin/nav-builder/NavGridItem.tsx +189 -0
  90. package/components/admin/nav-builder/NavItemSettings.tsx +288 -0
  91. package/components/admin/nav-builder/NavItemTypePicker.tsx +102 -0
  92. package/components/admin/nav-builder/NavLivePreview.tsx +125 -0
  93. package/components/admin/nav-builder/NavSettingsFields.tsx +248 -0
  94. package/components/admin/nav-builder/NavSettingsPanel.tsx +127 -0
  95. package/components/admin/nav-builder/index.ts +10 -0
  96. package/components/admin/nav-builder/nav-builder-utils.ts +238 -0
  97. package/components/admin/setup-wizard/BrandingStep.tsx +218 -0
  98. package/components/admin/setup-wizard/DatabaseStep.tsx +331 -0
  99. package/components/admin/setup-wizard/DoneStep.tsx +187 -0
  100. package/components/admin/setup-wizard/SetupWizard.tsx +166 -0
  101. package/components/admin/setup-wizard/StorageStep.tsx +308 -0
  102. package/components/admin/setup-wizard/WelcomeStep.tsx +96 -0
  103. package/components/admin/setup-wizard/index.ts +9 -0
  104. package/components/admin/styles/ColorsEditor.tsx +214 -0
  105. package/components/admin/styles/FontsEditor.tsx +258 -0
  106. package/components/admin/styles/GridLayoutEditor.tsx +292 -0
  107. package/components/admin/styles/LinksButtonsEditor.tsx +120 -0
  108. package/components/admin/styles/TypographyEditor.tsx +266 -0
  109. package/components/admin/styles/index.ts +9 -0
  110. package/components/admin/styles/shared.tsx +68 -0
  111. package/components/blocks/BlockRenderer.tsx +404 -0
  112. package/components/blocks/ButtonBlockRenderer.tsx +52 -0
  113. package/components/blocks/CoverBlockRenderer.tsx +239 -0
  114. package/components/blocks/CustomSectionInstanceRenderer.tsx +82 -0
  115. package/components/blocks/EnterAnimationWrapper.tsx +140 -0
  116. package/components/blocks/HoverAnimationWrapper.tsx +308 -0
  117. package/components/blocks/ImageBlockRenderer.tsx +61 -0
  118. package/components/blocks/ImageGridBlockRenderer.tsx +545 -0
  119. package/components/blocks/PageBackground.tsx +28 -0
  120. package/components/blocks/PageNavAnimation.tsx +35 -0
  121. package/components/blocks/PageNavColor.tsx +24 -0
  122. package/components/blocks/PageRenderer.tsx +142 -0
  123. package/components/blocks/ParallaxGroupRenderer.tsx +448 -0
  124. package/components/blocks/ParallaxSlideRenderer.tsx +175 -0
  125. package/components/blocks/ProjectGridBlockRenderer.tsx +556 -0
  126. package/components/blocks/SectionRenderer.tsx +170 -0
  127. package/components/blocks/SectionV2Renderer.tsx +330 -0
  128. package/components/blocks/ShaderCanvas.tsx +392 -0
  129. package/components/blocks/SpacerBlockRenderer.tsx +17 -0
  130. package/components/blocks/TextBlockRenderer.tsx +87 -0
  131. package/components/blocks/TypewriterRichText.tsx +464 -0
  132. package/components/blocks/TypewriterWrapper.tsx +149 -0
  133. package/components/blocks/VideoBlockRenderer.tsx +304 -0
  134. package/components/blocks/index.ts +2 -0
  135. package/components/builder/AssetBrowser.tsx +2 -0
  136. package/components/builder/BlockLivePreview.tsx +101 -0
  137. package/components/builder/BlockTypePicker.tsx +178 -0
  138. package/components/builder/BuilderCanvas.tsx +354 -0
  139. package/components/builder/CanvasMinimap.tsx +200 -0
  140. package/components/builder/CanvasToolbar.tsx +202 -0
  141. package/components/builder/ColorPicker.tsx +243 -0
  142. package/components/builder/ColorSwatchPicker.tsx +274 -0
  143. package/components/builder/ColumnDragContext.tsx +51 -0
  144. package/components/builder/ColumnDragOverlay.tsx +110 -0
  145. package/components/builder/CustomSectionInstanceCard.tsx +97 -0
  146. package/components/builder/DeviceFrame.tsx +123 -0
  147. package/components/builder/DndWrapper.tsx +337 -0
  148. package/components/builder/InsertionLines.tsx +186 -0
  149. package/components/builder/ParallaxGroupCanvas.tsx +228 -0
  150. package/components/builder/ParallaxSlideHeader.tsx +113 -0
  151. package/components/builder/ReadOnlyFrame.tsx +417 -0
  152. package/components/builder/SectionEditorBar.tsx +288 -0
  153. package/components/builder/SectionTypePicker.tsx +422 -0
  154. package/components/builder/SectionV2Canvas.tsx +297 -0
  155. package/components/builder/SectionV2Column.tsx +488 -0
  156. package/components/builder/SettingsPanel.tsx +911 -0
  157. package/components/builder/SortableBlock.tsx +230 -0
  158. package/components/builder/SortableRow.tsx +362 -0
  159. package/components/builder/VirtualAssetGrid.tsx +397 -0
  160. package/components/builder/asset-browser/AssetBrowser.tsx +178 -0
  161. package/components/builder/asset-browser/FileLightbox.tsx +116 -0
  162. package/components/builder/asset-browser/FolderTreeItem.tsx +55 -0
  163. package/components/builder/asset-browser/R2BrowserContent.tsx +436 -0
  164. package/components/builder/asset-browser/R2ContextMenu.tsx +98 -0
  165. package/components/builder/asset-browser/VideoThumbnail.tsx +63 -0
  166. package/components/builder/asset-browser/helpers.ts +88 -0
  167. package/components/builder/asset-browser/index.ts +1 -0
  168. package/components/builder/asset-browser/types.ts +49 -0
  169. package/components/builder/asset-browser/useAssetBrowser.ts +344 -0
  170. package/components/builder/asset-browser/useR2DragDrop.ts +116 -0
  171. package/components/builder/asset-browser/useR2Operations.ts +189 -0
  172. package/components/builder/blockStyles.tsx +295 -0
  173. package/components/builder/editors/ButtonBlockEditor.tsx +184 -0
  174. package/components/builder/editors/CoverBlockEditor.tsx +488 -0
  175. package/components/builder/editors/EnterAnimationPicker.tsx +297 -0
  176. package/components/builder/editors/HoverEffectPicker.tsx +209 -0
  177. package/components/builder/editors/ImageBlockEditor.tsx +206 -0
  178. package/components/builder/editors/ImageGridBlockEditor.tsx +386 -0
  179. package/components/builder/editors/ProjectGridEditor.tsx +648 -0
  180. package/components/builder/editors/SpacerBlockEditor.tsx +167 -0
  181. package/components/builder/editors/StaggerSettings.tsx +108 -0
  182. package/components/builder/editors/TextAlignmentIcons.tsx +39 -0
  183. package/components/builder/editors/TextBlockEditor.tsx +462 -0
  184. package/components/builder/editors/TextStylePicker.tsx +183 -0
  185. package/components/builder/editors/VideoBlockEditor.tsx +278 -0
  186. package/components/builder/editors/index.ts +10 -0
  187. package/components/builder/editors/shared.tsx +345 -0
  188. package/components/builder/hooks/useColumnDrag.ts +472 -0
  189. package/components/builder/hooks/useColumnResize.ts +221 -0
  190. package/components/builder/index.ts +12 -0
  191. package/components/builder/live-preview/LiveButtonPreview.tsx +38 -0
  192. package/components/builder/live-preview/LiveCoverPreview.tsx +146 -0
  193. package/components/builder/live-preview/LiveImageGridPreview.tsx +123 -0
  194. package/components/builder/live-preview/LiveImagePreview.tsx +107 -0
  195. package/components/builder/live-preview/LiveProjectGridPreview.tsx +1010 -0
  196. package/components/builder/live-preview/LiveSpacerPreview.tsx +9 -0
  197. package/components/builder/live-preview/LiveTextEditor.tsx +198 -0
  198. package/components/builder/live-preview/LiveVideoPreview.tsx +98 -0
  199. package/components/builder/live-preview/index.ts +10 -0
  200. package/components/builder/live-preview/shared.tsx +153 -0
  201. package/components/builder/settings-panel/BlockLayoutTab.tsx +532 -0
  202. package/components/builder/settings-panel/BlockSettings.tsx +94 -0
  203. package/components/builder/settings-panel/ColumnV2Settings.tsx +160 -0
  204. package/components/builder/settings-panel/LayoutTab.tsx +310 -0
  205. package/components/builder/settings-panel/PageSettings.tsx +200 -0
  206. package/components/builder/settings-panel/ParallaxGroupSettings.tsx +118 -0
  207. package/components/builder/settings-panel/ParallaxSlideSettings.tsx +178 -0
  208. package/components/builder/settings-panel/SectionV2AnimationTab.tsx +103 -0
  209. package/components/builder/settings-panel/SectionV2LayoutTab.tsx +312 -0
  210. package/components/builder/settings-panel/SectionV2Settings.tsx +323 -0
  211. package/components/builder/settings-panel/TRBLInputs.tsx +51 -0
  212. package/components/builder/settings-panel/index.ts +19 -0
  213. package/components/builder/settings-panel/responsive-helpers.ts +524 -0
  214. package/components/ui/CustomCursor.tsx +118 -0
  215. package/components/ui/NavContentLightbox.tsx +152 -0
  216. package/components/ui/Navbar.tsx +582 -0
  217. package/components/ui/PortfolioTracker.tsx +87 -0
  218. package/components/ui/ScrollToTop.tsx +47 -0
  219. package/lib/animation/enter-presets.ts +147 -0
  220. package/lib/animation/enter-resolve.ts +90 -0
  221. package/lib/animation/enter-types.ts +128 -0
  222. package/lib/animation/hover-effect-presets.ts +210 -0
  223. package/lib/animation/hover-effect-types.ts +126 -0
  224. package/lib/asset-retry.ts +111 -0
  225. package/lib/assets.ts +92 -0
  226. package/lib/audit.ts +35 -0
  227. package/lib/auth-token.ts +94 -0
  228. package/lib/auth.ts +13 -0
  229. package/lib/builder/cascade-helpers.ts +51 -0
  230. package/lib/builder/cascade.ts +533 -0
  231. package/lib/builder/constants.ts +103 -0
  232. package/lib/builder/defaults.ts +182 -0
  233. package/lib/builder/history.ts +48 -0
  234. package/lib/builder/index.ts +21 -0
  235. package/lib/builder/layout-styles.ts +344 -0
  236. package/lib/builder/masonry.ts +166 -0
  237. package/lib/builder/responsive.ts +156 -0
  238. package/lib/builder/serializer.ts +845 -0
  239. package/lib/builder/store-blocks.ts +193 -0
  240. package/lib/builder/store-canvas.ts +319 -0
  241. package/lib/builder/store-helpers.ts +490 -0
  242. package/lib/builder/store-sections.ts +709 -0
  243. package/lib/builder/store.ts +333 -0
  244. package/lib/builder/templates.ts +297 -0
  245. package/lib/builder/types.ts +374 -0
  246. package/lib/builder/utils.ts +37 -0
  247. package/lib/color-utils.ts +116 -0
  248. package/lib/config/index.ts +57 -0
  249. package/lib/config/types.ts +122 -0
  250. package/lib/contexts/AssetContext.tsx +79 -0
  251. package/lib/contexts/NavAnimationContext.tsx +44 -0
  252. package/lib/contexts/NavColorContext.tsx +38 -0
  253. package/lib/contexts/PageExitContext.tsx +194 -0
  254. package/lib/contexts/ThumbStatusContext.tsx +83 -0
  255. package/lib/csrf-client.ts +34 -0
  256. package/lib/csrf.ts +68 -0
  257. package/lib/format-utils.ts +24 -0
  258. package/lib/hooks/useViewport.ts +42 -0
  259. package/lib/logger.ts +81 -0
  260. package/lib/revalidate.ts +23 -0
  261. package/lib/sanitize.ts +91 -0
  262. package/lib/sanity/client.ts +8 -0
  263. package/lib/sanity/queries.ts +486 -0
  264. package/lib/sanity/types.ts +869 -0
  265. package/lib/sanity/writeClient.ts +24 -0
  266. package/lib/security.ts +402 -0
  267. package/lib/setup/detect.ts +156 -0
  268. package/lib/shader/glsl/index.ts +27 -0
  269. package/lib/shader/glsl/pixelate.ts +51 -0
  270. package/lib/shader/glsl/rgb-shift.ts +45 -0
  271. package/lib/shader/glsl/ripple.ts +46 -0
  272. package/lib/shader/glsl/vertex.ts +14 -0
  273. package/lib/storage/index.ts +211 -0
  274. package/lib/storage/r2-adapter.ts +286 -0
  275. package/lib/storage/types.ts +125 -0
  276. package/lib/styles/provider.tsx +267 -0
  277. package/lib/thumbnails/generate.ts +151 -0
  278. package/lib/utils.ts +6 -0
  279. package/package.json +212 -0
  280. package/sanity/compose.ts +65 -0
  281. package/sanity/sanity.config.ts +126 -0
  282. package/sanity/schemas/assetRegistry.ts +301 -0
  283. package/sanity/schemas/blocks/blockLayout.ts +90 -0
  284. package/sanity/schemas/blocks/buttonBlock.ts +82 -0
  285. package/sanity/schemas/blocks/coverBlock.ts +229 -0
  286. package/sanity/schemas/blocks/imageBlock.ts +58 -0
  287. package/sanity/schemas/blocks/imageGridBlock.ts +112 -0
  288. package/sanity/schemas/blocks/index.ts +9 -0
  289. package/sanity/schemas/blocks/projectGridBlock.ts +251 -0
  290. package/sanity/schemas/blocks/spacerBlock.ts +41 -0
  291. package/sanity/schemas/blocks/textBlock.ts +139 -0
  292. package/sanity/schemas/blocks/videoBlock.ts +80 -0
  293. package/sanity/schemas/customSection.ts +69 -0
  294. package/sanity/schemas/customSectionInstance.ts +163 -0
  295. package/sanity/schemas/index.ts +111 -0
  296. package/sanity/schemas/objects/enterAnimationConfig.ts +72 -0
  297. package/sanity/schemas/objects/hoverEffectConfig.ts +90 -0
  298. package/sanity/schemas/objects/parallaxGroup.ts +66 -0
  299. package/sanity/schemas/objects/parallaxSlide.ts +217 -0
  300. package/sanity/schemas/objects/typewriterConfig.ts +38 -0
  301. package/sanity/schemas/page.ts +162 -0
  302. package/sanity/schemas/pageSection.ts +157 -0
  303. package/sanity/schemas/pageSectionV2.ts +269 -0
  304. package/sanity/schemas/siteSettings.ts +256 -0
  305. package/sanity/schemas/siteStyles.ts +212 -0
  306. package/site/error.ts +4 -0
  307. package/site/index.ts +8 -0
  308. package/site/not-found.ts +4 -0
  309. package/site/page.ts +4 -0
  310. package/site/preview.ts +4 -0
  311. package/site/robots.ts +4 -0
  312. package/site/sitemap.ts +4 -0
  313. package/site/work.ts +4 -0
  314. package/studio/index.ts +4 -0
  315. package/styles/admin.css +85 -0
  316. package/styles/animations.css +237 -0
  317. package/styles/base.css +148 -0
  318. package/styles/globals.css +10 -0
  319. package/tsconfig.json +25 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Morphika Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @morphika/andami
2
+
3
+ A reusable Visual Page Builder framework for Next.js. Build custom websites with a drag-and-drop visual editor, Sanity CMS backend, and zero-config deployment.
4
+
5
+ ## Features
6
+
7
+ - **Visual Page Builder** — Infinite canvas editor with device previews (desktop, tablet, phone)
8
+ - **9 Content Blocks** — Text, Image, Image Grid, Video, Spacer, Button, Cover, Project Grid, Parallax
9
+ - **V2 Grid System** — 12-column CSS grid with push cascade engine and responsive overrides
10
+ - **Custom Sections** — Create reusable sections with per-instance setting overrides
11
+ - **Navigation Builder** — Visual 12-column grid editor with drag & drop
12
+ - **Animation System** — Enter animations, hover effects (CSS + WebGL shaders), page transitions
13
+ - **Asset Management** — Cloudflare R2 storage with browser, thumbnails, and CDN serving
14
+ - **Setup Wizard** — Guided onboarding for new sites
15
+ - **Sanity v3 CMS** — Structured content with custom schemas, GROQ queries, and ISR
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ npx create-andami-site my-site
21
+ cd my-site
22
+ npm install
23
+ npm run dev
24
+ ```
25
+
26
+ Visit `http://localhost:3000/admin` to start building.
27
+
28
+ ## Stack
29
+
30
+ Next.js 16 (App Router) · Sanity v3 · Tailwind CSS v4 · Zustand · @dnd-kit · TypeScript
31
+
32
+ ## Documentation
33
+
34
+ | Doc | Description |
35
+ |-----|-------------|
36
+ | [Getting Started](docs/GETTING-STARTED.md) | 5-minute quickstart |
37
+ | [Configuration](docs/CONFIGURATION.md) | `site.config.ts` reference |
38
+ | [Architecture](docs/ARCHITECTURE.md) | Technical architecture and data flow |
39
+ | [Blocks](docs/BLOCKS.md) | All block types with fields and options |
40
+ | [Builder](docs/BUILDER.md) | Page builder UI, interactions, store |
41
+ | [CMS](docs/CMS.md) | Sanity schemas and GROQ queries |
42
+ | [Assets](docs/ASSETS.md) | Storage, providers, thumbnails |
43
+ | [Customization](docs/CUSTOMIZATION.md) | Custom blocks, fonts, styles |
44
+ | [Deployment](docs/DEPLOYMENT.md) | Vercel, domains, R2 setup |
45
+ | [Publishing](docs/PUBLISHING.md) | npm package publishing workflow |
46
+ | [Development](docs/DEVELOPMENT.md) | Framework + instance dev workflow |
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/assets — Asset browser page.
3
+ */
4
+ export { default as AssetsPage } from "../app/admin/assets/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/database — Database configurator page.
3
+ */
4
+ export { default as DatabasePage } from "../app/admin/database/page";
package/admin/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @morphika/andami/admin — Admin page components.
3
+ * Re-exports from the monolith source during the transition period.
4
+ */
5
+ export { default as AdminLayout } from "../app/admin/layout";
6
+ export { default as AdminDashboard } from "../app/admin/page";
package/admin/login.ts ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/login — Admin login page.
3
+ */
4
+ export { default as LoginPage } from "../app/admin/login/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/navigation — Navigation builder page.
3
+ */
4
+ export { default as NavigationPage } from "../app/admin/navigation/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/pages/editor — Page editor page.
3
+ */
4
+ export { default as PageEditorPage } from "../app/admin/pages/[slug]/page";
package/admin/pages.ts ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/pages — Pages list page.
3
+ */
4
+ export { default as PagesListPage } from "../app/admin/pages/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/projects/editor — Project editor page.
3
+ */
4
+ export { default as ProjectEditorPage } from "../app/admin/projects/[slug]/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/projects — Projects list page.
3
+ */
4
+ export { default as ProjectsPage } from "../app/admin/projects/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/settings — Site settings page.
3
+ */
4
+ export { default as AdminSettingsPage } from "../app/admin/settings/page";
package/admin/setup.ts ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/setup — Setup wizard page re-export.
3
+ */
4
+ export { default } from "../app/admin/setup/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/storage — Storage management page.
3
+ */
4
+ export { default as StoragePage } from "../app/admin/storage/page";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @morphika/andami/admin/styles — Styles editor page.
3
+ */
4
+ export { default as StylesPage } from "../app/admin/styles/page";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Loading skeleton for dynamic pages.
3
+ * Shown by Next.js while page content loads.
4
+ */
5
+ export default function PageLoading() {
6
+ return (
7
+ <div className="min-h-screen bg-brand-dark" aria-busy="true" aria-label="Loading page">
8
+ {/* Hero placeholder */}
9
+ <div className="w-full h-[50vh] bg-neutral-900 animate-pulse" />
10
+ {/* Content placeholders */}
11
+ <div className="max-w-3xl mx-auto px-6 py-16 space-y-6">
12
+ <div className="h-8 w-1/2 bg-neutral-800 rounded animate-pulse" />
13
+ <div className="h-4 w-full bg-neutral-800/60 rounded animate-pulse" />
14
+ <div className="h-4 w-4/5 bg-neutral-800/60 rounded animate-pulse" />
15
+ <div className="h-96 w-full bg-neutral-800/40 rounded animate-pulse mt-8" />
16
+ <div className="h-4 w-3/4 bg-neutral-800/60 rounded animate-pulse" />
17
+ </div>
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,83 @@
1
+ import { notFound } from "next/navigation";
2
+ import type { Metadata } from "next";
3
+ import { client } from "../../../lib/sanity/client";
4
+ import { publishedPageBySlugQuery, allPageSlugsQuery } from "../../../lib/sanity/queries";
5
+ import type { Page } from "../../../lib/sanity/types";
6
+ import { PageRenderer } from "../../../components/blocks";
7
+ import { getSiteConfig } from "../../../lib/config";
8
+ import { assetUrl } from "../../../lib/assets";
9
+
10
+ const cfg = getSiteConfig();
11
+
12
+ // ISR: cache for 1 hour, revalidate in background on next request.
13
+ export const revalidate = 3600;
14
+
15
+ interface PageProps {
16
+ params: Promise<{ slug: string }>;
17
+ }
18
+
19
+ async function getPageBySlug(slug: string): Promise<Page | null> {
20
+ try {
21
+ return await client.fetch<Page>(publishedPageBySlugQuery, { slug });
22
+ } catch (error) {
23
+ console.error(`[Page] Failed to fetch page "${slug}":`, error);
24
+ return null;
25
+ }
26
+ }
27
+
28
+ export async function generateStaticParams() {
29
+ try {
30
+ const slugs: string[] = await client.fetch(allPageSlugsQuery);
31
+ return slugs.map((slug) => ({ slug }));
32
+ } catch {
33
+ return [];
34
+ }
35
+ }
36
+
37
+ export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
38
+ const { slug } = await params;
39
+ const page = await getPageBySlug(slug);
40
+
41
+ if (!page) return {};
42
+
43
+ const title = page.metadata?.seo_title || page.title;
44
+ const description = page.metadata?.seo_description || cfg.defaults.metaDescription;
45
+ const ogImagePath = page.metadata?.og_image_path || page.thumbnail_path;
46
+ const ogImage = ogImagePath ? assetUrl(ogImagePath) : undefined;
47
+ const url = `${cfg.domain}/${slug}`;
48
+
49
+ return {
50
+ title,
51
+ description,
52
+ alternates: { canonical: url },
53
+ openGraph: {
54
+ title,
55
+ description,
56
+ url,
57
+ siteName: cfg.name,
58
+ type: "website",
59
+ ...(ogImage && { images: [{ url: ogImage }] }),
60
+ },
61
+ twitter: {
62
+ card: "summary_large_image",
63
+ title,
64
+ description,
65
+ ...(ogImage && { images: [ogImage] }),
66
+ },
67
+ };
68
+ }
69
+
70
+ export default async function DynamicPage({ params }: PageProps) {
71
+ const { slug } = await params;
72
+ const page = await getPageBySlug(slug);
73
+
74
+ if (!page) {
75
+ notFound();
76
+ }
77
+
78
+ return (
79
+ <main className="min-h-screen">
80
+ <PageRenderer page={page} />
81
+ </main>
82
+ );
83
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+
5
+ export default function SiteError({
6
+ error,
7
+ reset,
8
+ }: {
9
+ error: Error & { digest?: string };
10
+ reset: () => void;
11
+ }) {
12
+ useEffect(() => {
13
+ console.error("[SiteError]", error);
14
+ }, [error]);
15
+
16
+ return (
17
+ <div className="min-h-[80vh] flex flex-col items-center justify-center px-6 text-center">
18
+ <h1 className="text-5xl font-bold tracking-tight text-white">
19
+ Something went wrong
20
+ </h1>
21
+ <p className="mt-4 text-lg text-white/60 max-w-md">
22
+ An unexpected error occurred. Please try again.
23
+ </p>
24
+ <button
25
+ onClick={reset}
26
+ className="mt-8 inline-flex items-center gap-2 px-6 py-3 rounded-full text-sm font-medium bg-white text-black transition-colors hover:bg-white/80"
27
+ >
28
+ Try again
29
+ </button>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,53 @@
1
+ import { client } from "../../lib/sanity/client";
2
+ import { siteSettingsQuery } from "../../lib/sanity/queries";
3
+ import type { SiteSettings } from "../../lib/sanity/types";
4
+ import { NavColorProvider } from "../../lib/contexts/NavColorContext";
5
+ import { NavAnimationProvider } from "../../lib/contexts/NavAnimationContext";
6
+ import { PageExitProvider } from "../../lib/contexts/PageExitContext";
7
+ import { StylesProvider } from "../../lib/styles/provider";
8
+ import { getSiteConfig } from "../../lib/config";
9
+ import Navbar from "../../components/ui/Navbar";
10
+ import CustomCursor from "../../components/ui/CustomCursor";
11
+ import ScrollToTop from "../../components/ui/ScrollToTop";
12
+ import PortfolioTracker from "../../components/ui/PortfolioTracker";
13
+
14
+ async function getSiteSettings(): Promise<SiteSettings | null> {
15
+ try {
16
+ return await client.fetch<SiteSettings>(siteSettingsQuery);
17
+ } catch (error) {
18
+ console.error("[SiteLayout] Failed to fetch site settings:", error);
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export default async function SiteLayout({
24
+ children,
25
+ }: {
26
+ children: React.ReactNode;
27
+ }) {
28
+ const settings = await getSiteSettings();
29
+ const navColor = settings?.nav_design?.color || "yellow-lime";
30
+
31
+ const cfg = getSiteConfig();
32
+
33
+ return (
34
+ <StylesProvider>
35
+ <NavColorProvider initialColor={navColor}>
36
+ <NavAnimationProvider>
37
+ <PageExitProvider>
38
+ <div data-site {...(cfg.features.customCursor ? { "data-custom-cursor": true } : {})}>
39
+ <Navbar
40
+ navItems={settings?.nav_items}
41
+ design={settings?.nav_design}
42
+ />
43
+ <main className="flex-1">{children}</main>
44
+ {cfg.features.customCursor && <CustomCursor />}
45
+ <ScrollToTop />
46
+ {cfg.features.portfolioTracking && <PortfolioTracker />}
47
+ </div>
48
+ </PageExitProvider>
49
+ </NavAnimationProvider>
50
+ </NavColorProvider>
51
+ </StylesProvider>
52
+ );
53
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Loading skeleton for public site pages.
3
+ * Shown by Next.js while dynamic page content loads.
4
+ */
5
+ export default function SiteLoading() {
6
+ return (
7
+ <div className="min-h-screen bg-brand-dark" aria-busy="true" aria-label="Loading page">
8
+ {/* Hero placeholder */}
9
+ <div className="w-full h-[60vh] bg-neutral-900 animate-pulse" />
10
+ {/* Content placeholders */}
11
+ <div className="max-w-3xl mx-auto px-6 py-16 space-y-6">
12
+ <div className="h-8 w-2/3 bg-neutral-800 rounded animate-pulse" />
13
+ <div className="h-4 w-full bg-neutral-800/60 rounded animate-pulse" />
14
+ <div className="h-4 w-5/6 bg-neutral-800/60 rounded animate-pulse" />
15
+ <div className="h-4 w-4/6 bg-neutral-800/60 rounded animate-pulse" />
16
+ <div className="h-64 w-full bg-neutral-800/40 rounded animate-pulse mt-8" />
17
+ </div>
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,41 @@
1
+ import Link from "next/link";
2
+ import { getSiteConfig } from "../../lib/config";
3
+
4
+ const cfg = getSiteConfig();
5
+
6
+ export default function NotFound() {
7
+ return (
8
+ <div className="min-h-[80vh] flex flex-col items-center justify-center px-6 text-center">
9
+ <h1
10
+ className="text-[120px] leading-none font-bold tracking-tighter"
11
+ style={{ color: cfg.palette.accent }}
12
+ >
13
+ 404
14
+ </h1>
15
+ <p className="mt-4 text-lg text-white/60 max-w-md">
16
+ The page you&apos;re looking for doesn&apos;t exist or has been moved.
17
+ </p>
18
+ <Link
19
+ href="/"
20
+ className="mt-8 inline-flex items-center gap-2 px-6 py-3 rounded-full text-sm font-medium transition-colors hover:opacity-80"
21
+ style={{
22
+ backgroundColor: cfg.palette.primary,
23
+ color: cfg.palette.text,
24
+ }}
25
+ >
26
+ <svg
27
+ width="16"
28
+ height="16"
29
+ viewBox="0 0 24 24"
30
+ fill="none"
31
+ stroke="currentColor"
32
+ strokeWidth="2"
33
+ aria-hidden="true"
34
+ >
35
+ <polyline points="15 18 9 12 15 6" />
36
+ </svg>
37
+ Back to Home
38
+ </Link>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,43 @@
1
+ import { client } from "../../lib/sanity/client";
2
+ import { homePageQuery } from "../../lib/sanity/queries";
3
+ import type { Page } from "../../lib/sanity/types";
4
+ import { PageRenderer } from "../../components/blocks";
5
+ import { getSiteConfig } from "../../lib/config";
6
+
7
+ // ISR: cache for 1 hour, revalidate in background on next request.
8
+ export const revalidate = 3600;
9
+
10
+ async function getHomePage(): Promise<Page | null> {
11
+ try {
12
+ return await client.fetch<Page>(homePageQuery);
13
+ } catch (error) {
14
+ console.error("[Home] Failed to fetch home page:", error);
15
+ return null;
16
+ }
17
+ }
18
+
19
+ export default async function HomePage() {
20
+ const page = await getHomePage();
21
+
22
+ if (!page) {
23
+ const cfg = getSiteConfig();
24
+ return (
25
+ <main className="flex min-h-screen items-center justify-center bg-brand-dark">
26
+ <div className="text-center">
27
+ <h1 className="font-mono text-2xl uppercase tracking-widest text-brand-accent">
28
+ {cfg.name}
29
+ </h1>
30
+ <p className="mt-4 font-mono text-sm uppercase tracking-wider text-brand-muted">
31
+ Coming Soon
32
+ </p>
33
+ </div>
34
+ </main>
35
+ );
36
+ }
37
+
38
+ return (
39
+ <main className="min-h-screen">
40
+ <PageRenderer page={page} />
41
+ </main>
42
+ );
43
+ }
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Preview Page — renders any page (including drafts) for the builder's
5
+ * "Preview" button. Fetches from /api/admin/preview with slug.
6
+ *
7
+ * URL pattern:
8
+ * /preview?slug=about → preview by slug
9
+ *
10
+ * Renders inside the (site) layout so it includes Navbar, Footer,
11
+ * custom cursor, etc. — exactly like the production page.
12
+ */
13
+
14
+ import { useSearchParams } from "next/navigation";
15
+ import { useEffect, useState, Suspense } from "react";
16
+ import type { Page } from "../../../lib/sanity/types";
17
+ import { PageRenderer } from "../../../components/blocks";
18
+ import { AssetProvider } from "../../../lib/contexts/AssetContext";
19
+
20
+ function PreviewContent() {
21
+ const searchParams = useSearchParams();
22
+ const slug = searchParams.get("slug");
23
+
24
+ const [page, setPage] = useState<Page | null>(null);
25
+ const [error, setError] = useState<string | null>(null);
26
+ const [loading, setLoading] = useState(true);
27
+
28
+ useEffect(() => {
29
+ if (!slug) {
30
+ setError("Missing ?slug= parameter");
31
+ setLoading(false);
32
+ return;
33
+ }
34
+
35
+ const params = new URLSearchParams();
36
+ params.set("slug", slug);
37
+
38
+ fetch(`/api/admin/preview?${params}`)
39
+ .then((res) => {
40
+ if (!res.ok) throw new Error(`Failed to load (${res.status})`);
41
+ return res.json();
42
+ })
43
+ .then((data) => {
44
+ setPage(data.page);
45
+ setLoading(false);
46
+ })
47
+ .catch((err) => {
48
+ setError(err.message);
49
+ setLoading(false);
50
+ });
51
+ }, [slug]);
52
+
53
+ if (loading) {
54
+ return (
55
+ <div className="flex min-h-[50vh] items-center justify-center">
56
+ <p className="font-mono text-sm text-neutral-400 animate-pulse">
57
+ Loading preview...
58
+ </p>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ if (error || !page) {
64
+ return (
65
+ <div className="flex min-h-[50vh] items-center justify-center">
66
+ <div className="text-center">
67
+ <p className="font-mono text-sm text-[var(--admin-error)]">
68
+ {error || "Page not found"}
69
+ </p>
70
+ <p className="mt-2 font-mono text-xs text-neutral-400">
71
+ Make sure the page has been saved at least once.
72
+ </p>
73
+ </div>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ return (
79
+ <AssetProvider mode="admin">
80
+ <PageRenderer page={page} />
81
+ </AssetProvider>
82
+ );
83
+ }
84
+
85
+ export default function PreviewPage() {
86
+ return (
87
+ <Suspense
88
+ fallback={
89
+ <div className="flex min-h-[50vh] items-center justify-center">
90
+ <p className="font-mono text-sm text-neutral-400 animate-pulse">
91
+ Loading preview...
92
+ </p>
93
+ </div>
94
+ }
95
+ >
96
+ <PreviewContent />
97
+ </Suspense>
98
+ );
99
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Loading skeleton for project detail pages.
3
+ * Shown by Next.js while project content loads.
4
+ */
5
+ export default function ProjectLoading() {
6
+ return (
7
+ <div className="min-h-screen bg-brand-dark" aria-busy="true" aria-label="Loading project">
8
+ {/* Hero placeholder */}
9
+ <div className="w-full h-[80vh] bg-neutral-900 animate-pulse" />
10
+ {/* Content placeholders */}
11
+ <div className="max-w-4xl mx-auto px-6 py-16 space-y-8">
12
+ <div className="h-6 w-1/3 bg-neutral-800 rounded animate-pulse" />
13
+ <div className="h-4 w-2/3 bg-neutral-800/60 rounded animate-pulse" />
14
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-8">
15
+ <div className="h-72 bg-neutral-800/40 rounded animate-pulse" />
16
+ <div className="h-72 bg-neutral-800/40 rounded animate-pulse" />
17
+ </div>
18
+ <div className="h-4 w-full bg-neutral-800/60 rounded animate-pulse" />
19
+ <div className="h-4 w-5/6 bg-neutral-800/60 rounded animate-pulse" />
20
+ </div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,84 @@
1
+ import { notFound } from "next/navigation";
2
+ import type { Metadata } from "next";
3
+ import { client } from "../../../../lib/sanity/client";
4
+ import { publishedProjectBySlugQuery, allProjectSlugsQuery } from "../../../../lib/sanity/queries";
5
+ import type { Page } from "../../../../lib/sanity/types";
6
+ import { PageRenderer } from "../../../../components/blocks";
7
+ import { getSiteConfig } from "../../../../lib/config";
8
+ import { assetUrl } from "../../../../lib/assets";
9
+
10
+ const cfg = getSiteConfig();
11
+
12
+ // ISR: cache for 1 hour, revalidate in background on next request.
13
+ // Replaces force-dynamic — reduces SSR invocations by ~95%.
14
+ export const revalidate = 3600;
15
+
16
+ interface ProjectPageProps {
17
+ params: Promise<{ slug: string }>;
18
+ }
19
+
20
+ async function getProjectPage(slug: string): Promise<Page | null> {
21
+ try {
22
+ return await client.fetch<Page>(publishedProjectBySlugQuery, { slug });
23
+ } catch (error) {
24
+ console.error(`[Project] Failed to fetch project "${slug}":`, error);
25
+ return null;
26
+ }
27
+ }
28
+
29
+ export async function generateStaticParams() {
30
+ try {
31
+ const slugs = await client.fetch<string[]>(allProjectSlugsQuery);
32
+ return slugs.map((slug) => ({ slug }));
33
+ } catch {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ export async function generateMetadata({ params }: ProjectPageProps): Promise<Metadata> {
39
+ const { slug } = await params;
40
+ const page = await getProjectPage(slug);
41
+
42
+ if (!page) return {};
43
+
44
+ const title = page.metadata?.seo_title || `${page.title} — ${cfg.name}`;
45
+ const description = page.metadata?.seo_description || cfg.defaults.metaDescription;
46
+ const ogImagePath = page.metadata?.og_image_path || page.thumbnail_path;
47
+ const ogImage = ogImagePath ? assetUrl(ogImagePath) : undefined;
48
+ const url = `${cfg.domain}/work/${slug}`;
49
+
50
+ return {
51
+ title,
52
+ description,
53
+ alternates: { canonical: url },
54
+ openGraph: {
55
+ title,
56
+ description,
57
+ url,
58
+ siteName: cfg.name,
59
+ type: "article",
60
+ ...(ogImage && { images: [{ url: ogImage }] }),
61
+ },
62
+ twitter: {
63
+ card: "summary_large_image",
64
+ title,
65
+ description,
66
+ ...(ogImage && { images: [ogImage] }),
67
+ },
68
+ };
69
+ }
70
+
71
+ export default async function ProjectPage({ params }: ProjectPageProps) {
72
+ const { slug } = await params;
73
+ const page = await getProjectPage(slug);
74
+
75
+ if (!page) {
76
+ notFound();
77
+ }
78
+
79
+ return (
80
+ <main className="min-h-screen">
81
+ <PageRenderer page={page} />
82
+ </main>
83
+ );
84
+ }