@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
@@ -0,0 +1,869 @@
1
+ // ============================================
2
+ // Primitives / shared
3
+ // ============================================
4
+
5
+ export type NavColorVariant =
6
+ | "yellow-lime"
7
+ | "yellow"
8
+ | "red-coral"
9
+ | "blue"
10
+ | "green"
11
+ | "white";
12
+
13
+ /** Minimal Portable Text block shape (Sanity block content). */
14
+ export interface PortableTextBlock {
15
+ _type: string;
16
+ _key: string;
17
+ children?: Array<{ _type: string; _key: string; text?: string; marks?: string[] }>;
18
+ markDefs?: Array<{ _type: string; _key: string; [key: string]: unknown }>;
19
+ style?: string;
20
+ level?: number;
21
+ listItem?: string;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ export type PortableTextContent = PortableTextBlock[];
26
+
27
+ export interface SanitySlug {
28
+ current: string;
29
+ }
30
+
31
+ // ============================================
32
+ // Responsive Overrides
33
+ // ============================================
34
+ // Blocks store their base properties (= desktop).
35
+ // Tablet and phone can override any visual/layout property.
36
+ // At render time we merge: { ...base, ...overrides[viewport] }.
37
+ // This keeps desktop as source of truth; smaller viewports only
38
+ // store what differs.
39
+
40
+ /**
41
+ * Per-viewport overrides for a block.
42
+ * Each key holds a partial set of the block's own properties.
43
+ * `_type` and `_key` are never overridden.
44
+ */
45
+ export interface ResponsiveOverrides<T> {
46
+ tablet?: Partial<Omit<T, "_type" | "_key" | "responsive">>;
47
+ phone?: Partial<Omit<T, "_type" | "_key" | "responsive">>;
48
+ }
49
+
50
+ // ============================================
51
+ // Enter Animation Config (Session 116)
52
+ // ============================================
53
+ // Unified "enter" trigger with above-the-fold detection.
54
+ // No intensity/range/trigger fields.
55
+
56
+ export { type EnterAnimationConfig, type TypewriterConfig } from "../../lib/animation/enter-types";
57
+
58
+ // ============================================
59
+ // Hover Effect Config — NEW (Session 116)
60
+ // ============================================
61
+ // Replaces both HoverAnimationConfig and ShaderEffectConfig.
62
+ // Block-level only (no cascade).
63
+
64
+ export { type HoverEffectConfig } from "../../lib/animation/hover-effect-types";
65
+
66
+ // ============================================
67
+ // Block Layout — shared styling for all blocks
68
+ // ============================================
69
+ // Same properties as Row layout (Semplice Module styling):
70
+ // Spacing (padding TRBL), Background, Offset (margin TRBL), Border.
71
+
72
+ export interface BlockLayout {
73
+ // Alignment
74
+ align_h?: "left" | "center" | "right";
75
+ align_v?: "top" | "center" | "bottom";
76
+ // Spacing (padding TRBL, px)
77
+ spacing_top?: string;
78
+ spacing_right?: string;
79
+ spacing_bottom?: string;
80
+ spacing_left?: string;
81
+ // Background
82
+ background_color?: string;
83
+ background_opacity?: number; // 0–100
84
+ background_image?: string; // asset path
85
+ background_size?: "cover" | "contain" | "auto";
86
+ background_position?: string;
87
+ background_repeat?: "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
88
+ // Offset (margin TRBL, px)
89
+ offset_top?: string;
90
+ offset_right?: string;
91
+ offset_bottom?: string;
92
+ offset_left?: string;
93
+ // Border
94
+ border_color?: string;
95
+ border_width?: string;
96
+ border_style?: "none" | "solid" | "dashed" | "dotted";
97
+ border_sides?: "all" | "top" | "right" | "bottom" | "left" | "top-bottom" | "left-right";
98
+ border_radius?: string;
99
+ }
100
+
101
+ // ============================================
102
+ // Block Types (7)
103
+ // ============================================
104
+
105
+ export interface TextBlock {
106
+ _type: "textBlock";
107
+ _key: string;
108
+ text?: PortableTextContent;
109
+ /** Optional reference to a global typography level (h1, h2, body, etc.) */
110
+ textStyle?: string;
111
+ /** Number of CSS columns (1 = default, 2–4 = multi-column).
112
+ * Column gap inherits the global grid gutter via var(--grid-gutter). */
113
+ columns?: number;
114
+ style?: {
115
+ fontSize?: number | string; // px value (number) or legacy string enum
116
+ color?: string;
117
+ alignment?: "left" | "center" | "right" | "justify";
118
+ fontWeight?: string; // "300" | "400" | "500" | "700" etc.
119
+ lineHeight?: string;
120
+ letterSpacing?: string;
121
+ maxWidth?: string;
122
+ opacity?: number;
123
+ textTransform?: "none" | "uppercase" | "lowercase" | "capitalize";
124
+ };
125
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
126
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
127
+ typewriter_config?: import("../../lib/animation/enter-types").TypewriterConfig;
128
+ layout?: BlockLayout;
129
+ responsive?: ResponsiveOverrides<TextBlock>;
130
+ }
131
+
132
+ export interface ImageBlock {
133
+ _type: "imageBlock";
134
+ _key: string;
135
+ asset_path: string;
136
+ alt?: string;
137
+ caption?: string;
138
+ width?: "full" | "contained" | "small";
139
+ aspect_ratio?: "auto" | "16:9" | "4:3" | "1:1" | "21:9";
140
+ lazy?: boolean;
141
+ border_radius?: string;
142
+ shadow?: boolean;
143
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
144
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
145
+ layout?: BlockLayout;
146
+ responsive?: ResponsiveOverrides<ImageBlock>;
147
+ }
148
+
149
+ export interface ImageGridBlock {
150
+ _type: "imageGridBlock";
151
+ _key: string;
152
+ images?: Array<{ asset_path: string; alt?: string }>;
153
+ h_gutter?: number;
154
+ v_gutter?: number;
155
+ images_per_row?: 1 | 2 | 4 | 6 | 12;
156
+ random_grid?: "disabled" | "small2-big4" | "small3-big6" | "small4-big8";
157
+ random_seed?: number;
158
+ lightbox?: boolean;
159
+ object_fit?: "cover" | "contain";
160
+ border_radius?: string;
161
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
162
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
163
+ layout?: BlockLayout;
164
+ responsive?: ResponsiveOverrides<ImageGridBlock>;
165
+ }
166
+
167
+ export interface VideoBlock {
168
+ _type: "videoBlock";
169
+ _key: string;
170
+ video_type: "vimeo" | "youtube" | "mp4" | "url";
171
+ url_or_path: string;
172
+ poster?: string;
173
+ autoplay?: boolean;
174
+ loop?: boolean;
175
+ muted?: boolean;
176
+ controls?: boolean;
177
+ width?: "full" | "contained";
178
+ aspect_ratio?: "16:9" | "21:9" | "4:3" | "auto";
179
+ border_radius?: string;
180
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
181
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
182
+ layout?: BlockLayout;
183
+ responsive?: ResponsiveOverrides<VideoBlock>;
184
+ }
185
+
186
+ export interface SpacerBlock {
187
+ _type: "spacerBlock";
188
+ _key: string;
189
+ height?: "small" | "medium" | "large" | "xlarge" | "custom";
190
+ custom_height?: number;
191
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
192
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
193
+ layout?: BlockLayout;
194
+ responsive?: ResponsiveOverrides<SpacerBlock>;
195
+ }
196
+
197
+ export interface ButtonBlock {
198
+ _type: "buttonBlock";
199
+ _key: string;
200
+ text: string;
201
+ url: string;
202
+ style?: "primary" | "secondary" | "outline" | "text";
203
+ size?: "small" | "medium" | "large";
204
+ target?: boolean;
205
+ full_width?: boolean;
206
+ alignment?: "left" | "center" | "right";
207
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
208
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
209
+ layout?: BlockLayout;
210
+ responsive?: ResponsiveOverrides<ButtonBlock>;
211
+ }
212
+
213
+ export interface CoverBlock {
214
+ _type: "coverBlock";
215
+ _key: string;
216
+ // Content
217
+ headline?: string;
218
+ subheadline?: string;
219
+ cta_button?: {
220
+ text?: string;
221
+ url?: string;
222
+ target_blank?: boolean;
223
+ style?: "primary" | "secondary" | "outline" | "text";
224
+ };
225
+ // Media
226
+ media_type?: "image" | "video";
227
+ media_path?: string;
228
+ video_poster?: string;
229
+ background_size?: "cover" | "contain" | "none";
230
+ background_position?: string;
231
+ background_repeat?: "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
232
+ // Overlay
233
+ overlay?: "none" | "dark" | "light" | "gradient-bottom" | "gradient-top";
234
+ overlay_opacity?: number;
235
+ // Layout
236
+ content_align_h?: "left" | "center" | "right";
237
+ content_align_v?: "top" | "center" | "bottom";
238
+ content_max_width?: string;
239
+ height?: "100vh" | "80vh" | "60vh" | "40vh" | "custom";
240
+ custom_height?: string;
241
+ mobile_height?: "same" | "100vh" | "80vh" | "60vh" | "500px" | "400px";
242
+ // Appearance
243
+ text_color?: string;
244
+ show_scroll_indicator?: boolean;
245
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
246
+ hover_effect?: import("../../lib/animation/hover-effect-types").HoverEffectConfig;
247
+ layout?: BlockLayout;
248
+ responsive?: ResponsiveOverrides<CoverBlock>;
249
+ }
250
+
251
+ // ============================================
252
+ // Project Grid Block v2 (template-only, Session 105)
253
+ // ============================================
254
+
255
+ export interface ProjectGridItemOverrides {
256
+ aspect_ratio_override?: "16/9" | "1/1" | "9/16" | null;
257
+ }
258
+
259
+ export interface ProjectGridItem {
260
+ _key: string;
261
+ project_slug: string;
262
+ custom_thumbnail?: string;
263
+ custom_subtitle?: string;
264
+ /** Per-card aspect ratio override — null/undefined = inherit grid-level ratios */
265
+ aspect_ratio_override?: "16/9" | "1/1" | "9/16" | null;
266
+ /** Viewport-specific per-card overrides (tablet/phone) */
267
+ responsive?: {
268
+ tablet?: ProjectGridItemOverrides;
269
+ phone?: ProjectGridItemOverrides;
270
+ };
271
+ }
272
+
273
+ /** Card entrance animation config for Project Grid */
274
+ export interface CardEntranceConfig {
275
+ enabled?: boolean; // default false
276
+ preset?: "fade" | "slide-up" | "scale"; // default "slide-up"
277
+ stagger_delay?: number; // ms between cards, default 80
278
+ duration?: number; // ms, default 500
279
+ }
280
+
281
+ export interface ProjectGridBlock {
282
+ _type: "projectGridBlock";
283
+ _key: string;
284
+ // ─── Grid Settings ───
285
+ columns: number; // 1–6, default 3
286
+ aspect_ratios: string[]; // ["16/9", "1/1", "9/16"] multi-select, min 1
287
+ gap_v: number; // Vertical gap in px, default 16
288
+ gap_h: number; // Horizontal gap in px, default 16
289
+ // ─── Appearance ───
290
+ hover_effect: "3d" | "scale" | "none"; // default "scale"
291
+ show_subtitle: boolean; // default true
292
+ border_radius: number; // px, default 0
293
+ // ─── Video ───
294
+ video_mode: "off" | "hover" | "autoloop"; // default "off"
295
+ // ─── Card Entrance Animation ───
296
+ card_entrance?: CardEntranceConfig;
297
+ // ─── Projects ───
298
+ projects: ProjectGridItem[];
299
+ // ─── Standard block fields ───
300
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
301
+ layout?: BlockLayout;
302
+ responsive?: ResponsiveOverrides<ProjectGridBlock>;
303
+ }
304
+
305
+ // ============================================
306
+ // Parallax V2 — Group + Slide types (Session 123)
307
+ // ============================================
308
+
309
+ export type NavEntrancePreset = "" | "fade-in" | "slide-down" | "blur-in";
310
+
311
+ /** A single parallax slide — contains a full V2 section (columns + blocks) with background media */
312
+ export interface ParallaxSlideV2 {
313
+ _key: string;
314
+ _type: "parallaxSlide";
315
+
316
+ // Background
317
+ background_type: "image" | "video"; // default "image"
318
+ background_image?: string; // asset path
319
+ background_video?: string; // asset path
320
+ background_position?: string; // CSS background-position, default "center center"
321
+ background_overlay_color?: string; // hex, default "#000000"
322
+ background_overlay_opacity?: number; // 0–100, default 0
323
+
324
+ // Nav color override — hex color applied to navbar while this slide is active
325
+ nav_color?: string; // hex, e.g. "#ffffff"
326
+
327
+ // V2 section data (same structure as PageSectionV2 internals)
328
+ columns: SectionColumn[];
329
+ section_settings: SectionV2Settings;
330
+ }
331
+
332
+ /** A group of parallax slides — lives inline in a page's content_rows */
333
+ export interface ParallaxGroup {
334
+ _type: "parallaxGroup";
335
+ _key: string;
336
+
337
+ // Slides
338
+ slides: ParallaxSlideV2[];
339
+
340
+ // Group-level settings
341
+ transition_effect: "parallax" | "crossfade" | "reveal"; // default "parallax"
342
+ snap_enabled: boolean; // default true
343
+ parallax_intensity: number; // 0.2–0.6, default 0.4 (hidden from UI, hardcoded)
344
+ }
345
+
346
+ // ============================================
347
+ // Page Section — first-class section type (Session 76)
348
+ // ============================================
349
+ // Replaces the old "matryoshka" approach where section blocks were
350
+ // wrapped in row → column → block. Sections are direct entries in
351
+ // content_rows alongside regular rows.
352
+
353
+ export type PageSectionType = "projectGrid";
354
+
355
+ export interface SectionSettings {
356
+ // Background
357
+ background_color?: string;
358
+ background_opacity?: number;
359
+ background_image?: string;
360
+ background_size?: "cover" | "contain" | "auto";
361
+ background_position?: string;
362
+ background_repeat?: "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
363
+ // Spacing (padding TRBL)
364
+ spacing_top?: string;
365
+ spacing_right?: string;
366
+ spacing_bottom?: string;
367
+ spacing_left?: string;
368
+ // Offset (margin TRBL)
369
+ offset_top?: string;
370
+ offset_right?: string;
371
+ offset_bottom?: string;
372
+ offset_left?: string;
373
+ // Border
374
+ border_color?: string;
375
+ border_width?: string;
376
+ border_style?: "none" | "solid" | "dashed" | "dotted";
377
+ border_sides?: "all" | "top" | "right" | "bottom" | "left" | "top-bottom" | "left-right";
378
+ border_radius?: string;
379
+ // Animation
380
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
381
+ }
382
+
383
+ /** Section block types that can appear inside a PageSection */
384
+ export type SectionBlock = ProjectGridBlock;
385
+
386
+ /** Section settings that can be overridden per-viewport */
387
+ export type SectionSettingsOverridable = Pick<SectionSettings,
388
+ "background_color" | "background_opacity" | "background_image" | "background_size" | "background_position" | "background_repeat" |
389
+ "spacing_top" | "spacing_right" | "spacing_bottom" | "spacing_left" |
390
+ "offset_top" | "offset_right" | "offset_bottom" | "offset_left" |
391
+ "border_color" | "border_width" | "border_style" | "border_sides" | "border_radius"
392
+ >;
393
+
394
+ export interface PageSection {
395
+ _type: "pageSection";
396
+ _key: string;
397
+ section_type: PageSectionType;
398
+ /** The actual section content — single block */
399
+ block: [SectionBlock];
400
+ settings?: SectionSettings;
401
+ /** BUG-013 fix: Per-viewport responsive overrides for section settings */
402
+ responsive?: {
403
+ tablet?: Partial<SectionSettingsOverridable>;
404
+ phone?: Partial<SectionSettingsOverridable>;
405
+ };
406
+ }
407
+
408
+ // ============================================
409
+ // Page Section V2 — V2 Grid System (Session 83)
410
+ // ============================================
411
+ // Flat list of columns with explicit grid positions.
412
+ // No "row" objects — rows are a visual artifact derived from grid_row values.
413
+
414
+ export type SectionV2Preset = "full" | "halves" | "thirds" | "quarters" | "1/3+2/3" | "2/3+1/3" | "custom";
415
+
416
+ export interface SectionV2Settings {
417
+ // Grid
418
+ preset: SectionV2Preset;
419
+ grid_columns: number; // default 12
420
+ col_gap: number; // px, horizontal
421
+ row_gap: number; // px, vertical
422
+ // Spacing (padding TRBL, px)
423
+ spacing_top?: string;
424
+ spacing_right?: string;
425
+ spacing_bottom?: string;
426
+ spacing_left?: string;
427
+ // Offset (margin TRBL, px)
428
+ offset_top?: string;
429
+ offset_right?: string;
430
+ offset_bottom?: string;
431
+ offset_left?: string;
432
+ // Background
433
+ background_color?: string;
434
+ background_opacity?: number;
435
+ background_image?: string;
436
+ background_size?: "cover" | "contain" | "auto";
437
+ background_position?: string;
438
+ background_repeat?: "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
439
+ // Border
440
+ border_color?: string;
441
+ border_width?: string;
442
+ border_style?: "none" | "solid" | "dashed" | "dotted";
443
+ border_sides?: "all" | "top" | "right" | "bottom" | "left" | "top-bottom" | "left-right";
444
+ border_radius?: string;
445
+ // Animation (cascade: page → section → column → block)
446
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
447
+ stagger?: {
448
+ enabled?: boolean;
449
+ delayPerChild?: number; // 50–300ms
450
+ direction?: "left-to-right" | "right-to-left";
451
+ };
452
+ }
453
+
454
+ export interface SectionColumn {
455
+ _key: string;
456
+ grid_column: number; // 1-based start position (1–12)
457
+ grid_row: number; // 1-based, computed by builder
458
+ span: number; // how many grid columns (1–grid_columns)
459
+ blocks: ContentBlock[]; // same block types as today
460
+ // NEW (Session 116) — column-level enter animation for 4-level cascade
461
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
462
+ }
463
+
464
+ export interface ColumnOverride {
465
+ _key: string; // matches SectionColumn._key
466
+ grid_column?: number;
467
+ grid_row?: number;
468
+ span?: number;
469
+ }
470
+
471
+ /** V2 section settings that can be overridden per-viewport */
472
+ export type SectionV2SettingsOverridable = Pick<SectionV2Settings,
473
+ "col_gap" | "row_gap" |
474
+ "spacing_top" | "spacing_right" | "spacing_bottom" | "spacing_left" |
475
+ "offset_top" | "offset_right" | "offset_bottom" | "offset_left" |
476
+ "background_color" | "background_opacity" |
477
+ "border_color" | "border_width" | "border_style" | "border_sides" | "border_radius" |
478
+ "enter_animation" | "stagger"
479
+ >;
480
+
481
+ export interface SectionV2ResponsiveOverride {
482
+ columns?: ColumnOverride[]; // per-column layout overrides
483
+ settings?: Partial<SectionV2SettingsOverridable>; // section-level overrides
484
+ }
485
+
486
+ export interface PageSectionV2 {
487
+ _type: "pageSectionV2";
488
+ _key: string;
489
+ section_type: "empty-v2";
490
+ columns: SectionColumn[];
491
+ settings: SectionV2Settings;
492
+ responsive?: {
493
+ tablet?: SectionV2ResponsiveOverride;
494
+ phone?: SectionV2ResponsiveOverride;
495
+ };
496
+ }
497
+
498
+ // ============================================
499
+ // Custom Section — reusable V2 section (Session 107)
500
+ // ============================================
501
+
502
+ /** Full custom section document (for editing) */
503
+ export interface CustomSection {
504
+ _id: string;
505
+ title: string;
506
+ slug: SanitySlug;
507
+ description?: string;
508
+ section: PageSectionV2;
509
+ thumbnail_path?: string;
510
+ updated_at?: string;
511
+ }
512
+
513
+ /** Lightweight list item (for picker) */
514
+ export interface CustomSectionListItem {
515
+ _id: string;
516
+ title: string;
517
+ slug: SanitySlug;
518
+ description?: string;
519
+ thumbnail_path?: string;
520
+ updated_at?: string;
521
+ }
522
+
523
+ /** Instance reference stored inside a page's content_rows */
524
+ export interface CustomSectionInstance {
525
+ _type: "customSectionInstance";
526
+ _key: string;
527
+ custom_section_id: string; // Sanity _id of the customSection document
528
+ custom_section_slug: string;
529
+ custom_section_title: string; // cached for display
530
+ /** Per-instance section settings overrides (spacing, background, border, animation).
531
+ * Merged on top of the custom section's base settings at render time.
532
+ * Content (columns/blocks) remains shared; only the "embedding" settings vary per-instance. */
533
+ settings_overrides?: Partial<SectionV2Settings>;
534
+ /** Per-instance responsive overrides (tablet/phone viewport-specific settings).
535
+ * Same structure as PageSectionV2.responsive but only affects this instance. */
536
+ responsive_overrides?: PageSectionV2["responsive"];
537
+ }
538
+
539
+ // ============================================
540
+ // Content Item — union of PageSection, PageSectionV2, and CustomSectionInstance
541
+ // ============================================
542
+ // Used in content_rows and the builder store.
543
+
544
+ export type ContentItem = PageSection | PageSectionV2 | CustomSectionInstance | ParallaxGroup;
545
+
546
+ /** Type guard: check if a content item is a PageSection (V1) */
547
+ export function isPageSection(item: ContentItem): item is PageSection {
548
+ return (item as PageSection)._type === "pageSection";
549
+ }
550
+
551
+ /** Type guard: check if a content item is a PageSectionV2 */
552
+ export function isPageSectionV2(item: ContentItem): item is PageSectionV2 {
553
+ return (item as PageSectionV2)._type === "pageSectionV2";
554
+ }
555
+
556
+ /** Type guard: check if a content item is a CustomSectionInstance */
557
+ export function isCustomSectionInstance(item: ContentItem): item is CustomSectionInstance {
558
+ return (item as CustomSectionInstance)._type === "customSectionInstance";
559
+ }
560
+
561
+ /** Type guard: check if a content item is a ParallaxGroup (Session 123) */
562
+ export function isParallaxGroup(item: ContentItem): item is ParallaxGroup {
563
+ return (item as ParallaxGroup)._type === "parallaxGroup";
564
+ }
565
+
566
+ // ============================================
567
+ // Shared references
568
+ // ============================================
569
+
570
+ export interface PageRef {
571
+ _id: string;
572
+ title: string;
573
+ slug: SanitySlug;
574
+ page_type: PageType;
575
+ }
576
+
577
+ // ============================================
578
+ // Union type for all blocks
579
+ // ============================================
580
+
581
+ export type ContentBlock =
582
+ | TextBlock
583
+ | ImageBlock
584
+ | ImageGridBlock
585
+ | VideoBlock
586
+ | SpacerBlock
587
+ | ButtonBlock
588
+ | CoverBlock
589
+ | ProjectGridBlock;
590
+
591
+ // ============================================
592
+ // Structural types
593
+ // ============================================
594
+
595
+ // ============================================
596
+ // Page types
597
+ // ============================================
598
+
599
+ export type PageType = "page" | "project";
600
+
601
+ export interface PageMetadata {
602
+ seo_title?: string;
603
+ seo_description?: string;
604
+ og_image_path?: string;
605
+ }
606
+
607
+ export interface Page {
608
+ _id: string;
609
+ title: string;
610
+ slug: SanitySlug;
611
+ page_type: PageType;
612
+ is_home?: boolean;
613
+ content_rows?: ContentItem[];
614
+ metadata?: PageMetadata;
615
+ published_at?: string;
616
+ draft_mode?: boolean;
617
+ thumbnail_path?: string;
618
+ cover_video?: string;
619
+ page_settings?: {
620
+ background_color?: string;
621
+ text_color?: string;
622
+ nav_color?: string;
623
+ enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
624
+ // ── Per-page nav entrance override ──
625
+ nav_entrance_animation?: NavEntrancePreset;
626
+ nav_entrance_duration?: number;
627
+ nav_entrance_delay?: number;
628
+ nav_entrance_disabled?: boolean;
629
+ };
630
+ }
631
+
632
+ export interface PageListItem {
633
+ _id: string;
634
+ title: string;
635
+ slug: SanitySlug;
636
+ page_type: PageType;
637
+ is_home?: boolean;
638
+ draft_mode?: boolean;
639
+ published_at?: string;
640
+ thumbnail_path?: string;
641
+ cover_video?: string;
642
+ }
643
+
644
+ // ============================================
645
+ // Site Settings
646
+ // ============================================
647
+
648
+ export interface NavItemStyleOverrides {
649
+ font_size?: number;
650
+ font_weight?: string;
651
+ font_family?: string;
652
+ color?: string; // hex
653
+ text_transform?: "none" | "uppercase" | "lowercase" | "capitalize";
654
+ vertical_align?: "top" | "middle" | "bottom";
655
+ }
656
+
657
+ export interface NavItem {
658
+ _key: string;
659
+ type: "logo" | "menu-item"; // NEW — item type
660
+ label: string;
661
+ logo_image?: string; // NEW — asset path for logo (optional)
662
+ link_type: "internal" | "external" | "content";
663
+ internal_page?: PageRef;
664
+ external_url?: string;
665
+ /** Content link: type of media shown in lightbox */
666
+ content_type?: "image" | "video-file" | "video-embed";
667
+ /** Content link: asset path for image or video file */
668
+ content_asset?: string;
669
+ /** Content link: embed URL for YouTube/Vimeo */
670
+ content_url?: string;
671
+ visible: boolean;
672
+ grid_column: number; // 1–12, required
673
+ column_span: number; // NEW — how many columns this item occupies (1–12)
674
+ style_overrides?: NavItemStyleOverrides; // NEW — per-item style overrides
675
+ }
676
+
677
+ export interface NavDesign {
678
+ logo_text?: string;
679
+ color?: NavColorVariant;
680
+ position?: "fixed" | "sticky" | "static";
681
+ hide_on_scroll?: boolean;
682
+ font_size?: number;
683
+ font_weight?: string;
684
+ font_family?: string;
685
+ text_align?: "left" | "center" | "right";
686
+ text_transform?: "none" | "uppercase" | "lowercase" | "capitalize";
687
+ vertical_align?: "top" | "middle" | "bottom";
688
+ padding_h?: number;
689
+ padding_v?: number;
690
+ margin_h?: number;
691
+ margin_v?: number;
692
+ background_color?: string;
693
+ background_opacity?: number;
694
+ backdrop_blur?: boolean;
695
+ items_gap?: number;
696
+ logo_columns?: number; // 1–6, how many grid columns the logo spans
697
+ // ── Entrance animation ──
698
+ entrance_animation?: NavEntrancePreset;
699
+ entrance_duration?: number; // ms, default 600
700
+ entrance_delay?: number; // ms, default 0
701
+ entrance_stagger?: boolean; // stagger items, default false
702
+ entrance_stagger_delay?: number; // ms between items, default 80
703
+ }
704
+
705
+ // ============================================
706
+ // Storage Provider
707
+ // ============================================
708
+
709
+ export type StorageProviderType = "r2";
710
+
711
+ // ============================================
712
+ // Asset Registry
713
+ // ============================================
714
+
715
+ export interface RegisteredAsset {
716
+ _key: string;
717
+ path: string;
718
+ filename: string;
719
+ extension: string;
720
+ file_size?: number;
721
+ mime_type?: string;
722
+ width?: number;
723
+ height?: number;
724
+ file_hash?: string;
725
+ has_thumbnail?: boolean;
726
+ status: "active" | "missing" | "moved" | "new";
727
+ last_checked_at?: string;
728
+ used_in?: string[];
729
+ previous_paths?: string[];
730
+ }
731
+
732
+ export interface RelinkLogEntry {
733
+ _key: string;
734
+ date: string;
735
+ old_seed: string;
736
+ new_seed: string;
737
+ assets_relinked: number;
738
+ assets_missing: number;
739
+ details?: string;
740
+ }
741
+
742
+ export interface AssetRegistry {
743
+ _id: string;
744
+ /** Active storage provider. Defaults to "r2". */
745
+ storage_provider?: StorageProviderType;
746
+ seed_url?: string;
747
+ scan_status?: "ready" | "scanning" | "error";
748
+ last_scanned_at?: string;
749
+ scan_error?: string;
750
+ assets?: RegisteredAsset[];
751
+ relink_log?: RelinkLogEntry[];
752
+ // R2 configuration
753
+ r2_bucket_url?: string;
754
+ r2_access_key_id?: string;
755
+ r2_secret_access_key?: string;
756
+ r2_endpoint?: string;
757
+ r2_bucket_name?: string;
758
+ r2_connected_at?: string;
759
+ }
760
+
761
+ // ============================================
762
+ // Site Settings
763
+ // ============================================
764
+
765
+ export interface SiteSettings {
766
+ nav_items?: NavItem[];
767
+ nav_design?: NavDesign;
768
+ default_title?: string;
769
+ default_description?: string;
770
+ default_og_image?: string;
771
+ favicon_path?: string;
772
+ analytics_id?: string;
773
+ seed_url?: string;
774
+ last_scanned_at?: string;
775
+ asset_status?: "ok" | "scanning" | "error";
776
+ }
777
+
778
+ // ============================================
779
+ // Site Styles — Global design system
780
+ // ============================================
781
+
782
+ export interface FontVariant {
783
+ _key: string;
784
+ weight: string; // "300", "400", "500", "700", etc.
785
+ style: "normal" | "italic";
786
+ file_url: string; // Sanity CDN URL
787
+ file_id?: string; // Sanity asset ID (for deletion)
788
+ original_filename: string;
789
+ }
790
+
791
+ export interface FontFamily {
792
+ _key: string;
793
+ family: string; // e.g. "Inter", "Space Grotesk"
794
+ is_builtin?: boolean; // true for fonts already in /public/fonts/
795
+ variants: FontVariant[];
796
+ }
797
+
798
+ export interface TypographyLevel {
799
+ font_family?: string; // References FontFamily.family
800
+ font_size: string; // e.g. "3rem", "48px"
801
+ font_weight: string; // e.g. "700"
802
+ line_height: string; // e.g. "1.1"
803
+ letter_spacing: string; // e.g. "-0.02em"
804
+ text_transform?: "none" | "uppercase" | "lowercase" | "capitalize";
805
+ color?: string; // Override page text color
806
+ }
807
+
808
+ export interface ColorSwatch {
809
+ _key?: string;
810
+ name: string;
811
+ hex: string;
812
+ }
813
+
814
+ export interface ColorPalette {
815
+ swatches: ColorSwatch[];
816
+ // Legacy fields (backward compat, may be empty)
817
+ background?: string;
818
+ text?: string;
819
+ primary?: string;
820
+ secondary?: string;
821
+ accent?: string;
822
+ muted?: string;
823
+ }
824
+
825
+ export interface SpacingScale {
826
+ section_padding: string; // e.g. "80px"
827
+ content_gap: string; // e.g. "24px"
828
+ border_radius: string; // e.g. "8px"
829
+ }
830
+
831
+ export interface LinkStyle {
832
+ color: string;
833
+ hover_color: string;
834
+ underline: boolean;
835
+ }
836
+
837
+ export interface ButtonStyle {
838
+ primary_bg: string;
839
+ primary_text: string;
840
+ secondary_bg: string;
841
+ secondary_text: string;
842
+ border_radius: string;
843
+ }
844
+
845
+ export interface GridSettings {
846
+ width: string; // e.g. "1445"
847
+ outer_padding: string; // e.g. "30"
848
+ gutter_desktop: string; // e.g. "30"
849
+ gutter_responsive: string; // e.g. "30" (tablet)
850
+ gutter_phone?: string; // e.g. "16" (phone, ≤640px)
851
+ }
852
+
853
+ export interface SiteStyles {
854
+ grid?: GridSettings;
855
+ fonts?: FontFamily[];
856
+ typography?: {
857
+ h1?: TypographyLevel;
858
+ h2?: TypographyLevel;
859
+ h3?: TypographyLevel;
860
+ h4?: TypographyLevel;
861
+ body?: TypographyLevel;
862
+ small?: TypographyLevel;
863
+ };
864
+ colors?: ColorPalette;
865
+ link_style?: LinkStyle;
866
+ button_style?: ButtonStyle;
867
+ /** When true, disables all scroll animations on viewports ≤768px */
868
+ disable_scroll_animations_mobile?: boolean;
869
+ }