@gradial/aci 0.1.20-preview.3 → 0.1.21

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 (227) hide show
  1. package/README.md +48 -108
  2. package/bin/{aci → aci-darwin-arm64} +0 -0
  3. package/bin/aci.js +99 -6
  4. package/dist/assets/index.d.ts +3 -3
  5. package/dist/assets/index.js +3 -3
  6. package/dist/astro/index.d.ts +12 -3
  7. package/dist/astro/index.js +20 -5
  8. package/dist/content/assets.d.ts +17 -0
  9. package/dist/content/assets.js +65 -0
  10. package/dist/content/index.d.ts +1 -0
  11. package/dist/content/index.js +1 -0
  12. package/dist/define-component.d.ts +1 -2
  13. package/dist/define-component.js +73 -5
  14. package/dist/dev/content-watch.d.ts +37 -0
  15. package/dist/dev/content-watch.js +154 -0
  16. package/dist/dev/index.d.ts +24 -43
  17. package/dist/dev/index.js +4 -169
  18. package/dist/index.d.ts +14 -6
  19. package/dist/index.js +8 -9
  20. package/dist/next/asset-route.js +2 -2
  21. package/dist/next/config.d.ts +2 -2
  22. package/dist/next/config.js +5 -5
  23. package/dist/next/content-watch.js +6 -1
  24. package/dist/next/index.d.ts +2 -2
  25. package/dist/next/index.js +2 -2
  26. package/dist/next/middleware.d.ts +1 -0
  27. package/dist/next/middleware.js +49 -8
  28. package/dist/next/page.d.ts +1 -1
  29. package/dist/next/page.js +14 -12
  30. package/dist/next/render.d.ts +3 -3
  31. package/dist/next/render.js +11 -9
  32. package/dist/next/server.d.ts +12 -12
  33. package/dist/next/server.js +1 -1
  34. package/dist/preview/core.d.ts +5 -0
  35. package/dist/preview/core.js +1 -1
  36. package/dist/react/Media.d.ts +24 -0
  37. package/dist/react/Media.js +30 -0
  38. package/dist/react/Picture.d.ts +12 -0
  39. package/dist/react/Picture.js +11 -0
  40. package/dist/react/VideoPlayer.d.ts +13 -0
  41. package/dist/react/{GradialVideoPlayer.js → VideoPlayer.js} +6 -5
  42. package/dist/react/index.d.ts +333 -3
  43. package/dist/react/index.js +89 -3
  44. package/dist/registry.d.ts +2 -2
  45. package/dist/registry.js +3 -3
  46. package/dist/types/block-ref.d.ts +26 -0
  47. package/dist/types/block-ref.js +28 -0
  48. package/dist/types/component.d.ts +36 -32
  49. package/dist/types/config.d.ts +1 -1
  50. package/dist/types/image.d.ts +33 -20
  51. package/dist/types/image.js +42 -50
  52. package/dist/types/index.d.ts +1 -0
  53. package/dist/types/index.js +1 -0
  54. package/dist/types/link.d.ts +38 -0
  55. package/dist/types/link.js +58 -0
  56. package/dist/types/media.d.ts +16 -35
  57. package/dist/types/media.js +15 -84
  58. package/dist/types/renderer.d.ts +1 -1
  59. package/dist/types/rich-text.d.ts +40 -0
  60. package/dist/types/rich-text.js +39 -0
  61. package/dist/types/video.d.ts +7 -13
  62. package/dist/types/video.js +3 -3
  63. package/package.json +29 -76
  64. package/src/cli/compile-registry.mjs +120 -63
  65. package/src/cli/validate-content.mjs +38 -75
  66. package/src/types/block-ref.ts +74 -0
  67. package/src/types/image.ts +79 -60
  68. package/templates/astro/template/.aci.yaml +28 -0
  69. package/templates/astro/template/.storybook/main.ts +79 -0
  70. package/templates/astro/template/.storybook/manager.ts +6 -0
  71. package/templates/astro/template/.storybook/preview.ts +33 -0
  72. package/templates/astro/template/astro.config.mjs +37 -0
  73. package/templates/astro/template/package.json.tmpl +41 -0
  74. package/templates/astro/template/src/cms/contracts/layouts/index.ts +18 -0
  75. package/templates/astro/template/src/cms/registry.ts +28 -0
  76. package/templates/astro/template/src/components/chrome/Footer.astro +107 -0
  77. package/templates/astro/template/src/components/chrome/Header.astro +94 -0
  78. package/templates/astro/template/src/components/elements/AtomicBlock.astro +15 -0
  79. package/templates/astro/template/src/components/elements/AtomicBlocks.astro +16 -0
  80. package/templates/astro/template/src/components/elements/Button.astro +51 -0
  81. package/templates/astro/template/src/components/elements/ButtonBlock.astro +25 -0
  82. package/templates/astro/template/src/components/elements/Container.astro +11 -0
  83. package/templates/astro/template/src/components/elements/Icon.astro +31 -0
  84. package/templates/astro/template/src/components/elements/Logo.astro +29 -0
  85. package/templates/astro/template/src/components/elements/RemoteImage.astro +41 -0
  86. package/templates/astro/template/src/components/sections/ContainerSection.astro +32 -0
  87. package/templates/astro/template/src/components/sections/FeatureCard.astro +23 -0
  88. package/templates/astro/template/src/components/sections/FeatureGrid.astro +35 -0
  89. package/templates/astro/template/src/components/sections/HomeHero.astro +51 -0
  90. package/templates/astro/template/src/components/sections/StatsBar.astro +33 -0
  91. package/templates/astro/template/src/design-system/styles.css +399 -0
  92. package/templates/astro/template/src/design-system/tokens/colors.css +60 -0
  93. package/templates/astro/template/src/design-system/tokens/components.css +69 -0
  94. package/templates/astro/template/src/design-system/tokens/index.css +6 -0
  95. package/templates/astro/template/src/design-system/tokens/radii.css +5 -0
  96. package/templates/astro/template/src/design-system/tokens/shadows.css +5 -0
  97. package/templates/astro/template/src/design-system/tokens/spacing.css +13 -0
  98. package/templates/astro/template/src/design-system/tokens/typography.css +24 -0
  99. package/templates/astro/template/src/env.d.ts +1 -0
  100. package/templates/astro/template/src/fixtures/atomicBlocks.ts +33 -0
  101. package/templates/astro/template/src/fixtures/composition.ts +31 -0
  102. package/templates/astro/template/src/fixtures/index.ts +17 -0
  103. package/templates/astro/template/src/fixtures/sections.ts +39 -0
  104. package/templates/astro/template/src/layouts/SiteLayout.astro +30 -0
  105. package/templates/astro/template/src/pages/[...slug].astro +61 -0
  106. package/templates/astro/template/src/render/RenderPage.astro +66 -0
  107. package/templates/astro/template/src/stories/CompositionStoryFrame.astro +15 -0
  108. package/templates/astro/template/src/stories/HomeHero.stories.ts +24 -0
  109. package/templates/astro/template/src/stories/SectionStoryFrame.astro +16 -0
  110. package/templates/astro/template/src/stories/StoryDocFrame.astro +31 -0
  111. package/templates/astro/template/tsconfig.json +11 -0
  112. package/templates/common/.agents/AGENTS.md +37 -0
  113. package/templates/common/.agents/skills/aci/SKILL.md +162 -0
  114. package/templates/common/.agents/skills/authoring/SKILL.md +590 -0
  115. package/templates/common/.agents/skills/authoring/references/block-registry.md +151 -0
  116. package/templates/common/.agents/skills/authoring/references/contracts.md +153 -0
  117. package/templates/common/.agents/skills/authoring/references/design-system.md +171 -0
  118. package/templates/common/.agents/skills/authoring/references/image-assets.md +73 -0
  119. package/templates/common/.agents/skills/authoring/references/repo-structure.md +157 -0
  120. package/templates/common/.agents/skills/migration/SKILL.md +160 -0
  121. package/templates/common/.agents/skills/migration/references/analyzing-source.md +215 -0
  122. package/templates/common/.agents/skills/migration/references/component-migration.md +200 -0
  123. package/templates/common/.agents/skills/migration/references/selector-map.md +31 -0
  124. package/templates/common/.content/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
  125. package/templates/common/.content/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
  126. package/templates/common/.content/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
  127. package/templates/common/.content/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
  128. package/templates/common/.content/config/site.json +192 -0
  129. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/asset.json +9 -0
  130. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/editorial.json +14 -0
  131. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/governance.json +23 -0
  132. package/templates/common/.content/dam/folders/seed/ast_site_about_hero/versions/v0001.json +37 -0
  133. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/asset.json +9 -0
  134. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/editorial.json +14 -0
  135. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/governance.json +23 -0
  136. package/templates/common/.content/dam/folders/seed/ast_site_home_hero/versions/v0001.json +37 -0
  137. package/templates/common/.content/fragments/navbar/_index.json +25 -0
  138. package/templates/common/.content/fragments/site-footer/_index.json +62 -0
  139. package/templates/common/.content/pages/about/_index.json +51 -0
  140. package/templates/common/.content/pages/home/_index.json +112 -0
  141. package/templates/common/.content/schemas/site.schema.json +123 -0
  142. package/templates/common/README.md.tmpl +45 -0
  143. package/templates/common/contracts/components/button.contract.ts +19 -0
  144. package/templates/common/contracts/components/container.contract.ts +19 -0
  145. package/templates/common/contracts/components/featureCard.contract.ts +15 -0
  146. package/templates/common/contracts/components/featureGrid.contract.ts +16 -0
  147. package/templates/common/contracts/components/homeHero.contract.ts +36 -0
  148. package/templates/common/contracts/components/index.ts +21 -0
  149. package/templates/common/contracts/components/siteFooter.contract.ts +45 -0
  150. package/templates/common/contracts/components/siteNavigation.contract.ts +30 -0
  151. package/templates/common/contracts/components/statsBar.contract.ts +19 -0
  152. package/templates/common/design-system/styles.css +346 -0
  153. package/templates/common/design-system/tokens/colors.css +110 -0
  154. package/templates/common/design-system/tokens/components.css +86 -0
  155. package/templates/common/design-system/tokens/index.css +6 -0
  156. package/templates/common/design-system/tokens/radii.css +5 -0
  157. package/templates/common/design-system/tokens/shadows.css +9 -0
  158. package/templates/common/design-system/tokens/spacing.css +26 -0
  159. package/templates/common/design-system/tokens/typography.css +32 -0
  160. package/templates/common/eslint-rules/no-raw-tailwind-values.js +127 -0
  161. package/templates/common/public/assets/Dwire-model.png +0 -0
  162. package/templates/common/public/assets/bare-metal-logo.png +0 -0
  163. package/templates/common/public/assets/meridian-logo.svg +8 -0
  164. package/templates/common/public/assets/seattle.avif +0 -0
  165. package/templates/nextjs/template/.aci.yaml +26 -0
  166. package/templates/nextjs/template/.storybook/main.ts +13 -0
  167. package/templates/nextjs/template/.storybook/preview.ts +34 -0
  168. package/templates/nextjs/template/eslint.config.mjs +36 -0
  169. package/templates/nextjs/template/next-env.d.ts +6 -0
  170. package/templates/nextjs/template/next.config.ts +8 -0
  171. package/templates/nextjs/template/package.json.tmpl +46 -0
  172. package/templates/nextjs/template/postcss.config.mjs +7 -0
  173. package/templates/nextjs/template/src/app/[[...slug]]/page.tsx +8 -0
  174. package/templates/nextjs/template/src/app/gradial/assets/[releaseId]/[...path]/route.ts +31 -0
  175. package/templates/nextjs/template/src/app/layout.tsx +29 -0
  176. package/templates/nextjs/template/src/cms/contracts/components/button.contract.ts +19 -0
  177. package/templates/nextjs/template/src/cms/contracts/components/container.contract.ts +20 -0
  178. package/templates/nextjs/template/src/cms/contracts/components/homeHero.contract.ts +36 -0
  179. package/templates/nextjs/template/src/cms/contracts/components/index.ts +15 -0
  180. package/templates/nextjs/template/src/cms/contracts/components/siteFooter.contract.ts +45 -0
  181. package/templates/nextjs/template/src/cms/contracts/components/siteNavigation.contract.ts +30 -0
  182. package/templates/nextjs/template/src/cms/contracts/layouts/index.ts +16 -0
  183. package/templates/nextjs/template/src/cms/registry.ts +28 -0
  184. package/templates/nextjs/template/src/components/chrome/Footer.tsx +86 -0
  185. package/templates/nextjs/template/src/components/chrome/Header.tsx +74 -0
  186. package/templates/nextjs/template/src/components/elements/AtomicBlock.tsx +13 -0
  187. package/templates/nextjs/template/src/components/elements/Button.tsx +40 -0
  188. package/templates/nextjs/template/src/components/elements/ButtonBlock.tsx +18 -0
  189. package/templates/nextjs/template/src/components/elements/Container.tsx +14 -0
  190. package/templates/nextjs/template/src/components/elements/Icon.tsx +29 -0
  191. package/templates/nextjs/template/src/components/elements/Logo.tsx +38 -0
  192. package/templates/nextjs/template/src/components/sections/ContainerSection.tsx +28 -0
  193. package/templates/nextjs/template/src/components/sections/FeatureCard.tsx +25 -0
  194. package/templates/nextjs/template/src/components/sections/FeatureGrid.tsx +36 -0
  195. package/templates/nextjs/template/src/components/sections/HomeHero.tsx +62 -0
  196. package/templates/nextjs/template/src/components/sections/StatsBar.tsx +30 -0
  197. package/templates/nextjs/template/src/middleware.ts +4 -0
  198. package/templates/nextjs/template/src/stories/Button.stories.tsx +38 -0
  199. package/templates/nextjs/template/src/stories/Header.stories.tsx +30 -0
  200. package/templates/nextjs/template/src/stories/HomeHero.stories.tsx +33 -0
  201. package/templates/nextjs/template/tsconfig.json +23 -0
  202. package/templates/nextjs/template/vercel.json +6 -0
  203. package/dist/content/contract.d.ts +0 -83
  204. package/dist/content/contract.js +0 -104
  205. package/dist/content/tailwind-validator.d.ts +0 -6
  206. package/dist/content/tailwind-validator.js +0 -31
  207. package/dist/content/validation.d.ts +0 -108
  208. package/dist/content/validation.js +0 -183
  209. package/dist/react/GradialMedia.d.ts +0 -24
  210. package/dist/react/GradialMedia.js +0 -31
  211. package/dist/react/GradialPicture.d.ts +0 -14
  212. package/dist/react/GradialPicture.js +0 -30
  213. package/dist/react/GradialVideoPlayer.d.ts +0 -13
  214. package/dist/sveltekit/index.d.ts +0 -25
  215. package/dist/sveltekit/index.js +0 -66
  216. package/dist/sveltekit/preview.d.ts +0 -5
  217. package/dist/sveltekit/preview.js +0 -33
  218. package/src/types/component.ts +0 -49
  219. package/src/types/config.ts +0 -37
  220. package/src/types/data.ts +0 -47
  221. package/src/types/index.ts +0 -10
  222. package/src/types/layout.ts +0 -29
  223. package/src/types/media.ts +0 -125
  224. package/src/types/page.ts +0 -47
  225. package/src/types/render-mode.ts +0 -10
  226. package/src/types/renderer.ts +0 -83
  227. package/src/types/video.ts +0 -66
@@ -0,0 +1,74 @@
1
+ import { z } from 'zod';
2
+ import type { ComponentContract } from './component.js';
3
+
4
+ const BLOCK_REF_KEY = '_gradialBlockRef';
5
+
6
+ export interface BlockRefMeta {
7
+ allowed: readonly string[];
8
+ multiple: boolean;
9
+ }
10
+
11
+ const blockRefBase = z.object({
12
+ id: z.string().min(1),
13
+ component: z.string().min(1),
14
+ props: z.record(z.string(), z.unknown()),
15
+ });
16
+
17
+ type InferContractProps<T> = T extends ComponentContract<string, infer Schema>
18
+ ? Schema extends z.ZodType<infer P> ? P : Record<string, unknown>
19
+ : Record<string, unknown>;
20
+
21
+ type InferContractId<T> = T extends ComponentContract<infer Id, unknown> ? Id : never;
22
+
23
+ export type BlockRefEntry<T extends ComponentContract> = {
24
+ id: string;
25
+ component: InferContractId<T>;
26
+ props: InferContractProps<T>;
27
+ };
28
+
29
+ export type BlockRefUnion<T extends readonly ComponentContract[]> = {
30
+ [K in keyof T]: BlockRefEntry<T[K]>;
31
+ }[number];
32
+
33
+ function attachMeta<T extends z.ZodTypeAny>(
34
+ schema: T,
35
+ allowed: readonly string[],
36
+ multiple: boolean,
37
+ ): T {
38
+ Object.defineProperty(schema, BLOCK_REF_KEY, {
39
+ value: { allowed, multiple } satisfies BlockRefMeta,
40
+ enumerable: false,
41
+ writable: false,
42
+ configurable: false,
43
+ });
44
+ return schema;
45
+ }
46
+
47
+ export function blockRefArray<const T extends readonly ComponentContract[]>(
48
+ contracts: T,
49
+ ): z.ZodArray<z.ZodType<BlockRefUnion<T>>> {
50
+ return attachMeta(
51
+ z.array(blockRefBase) as unknown as z.ZodArray<z.ZodType<BlockRefUnion<T>>>,
52
+ contracts.map((c) => c.id),
53
+ true,
54
+ );
55
+ }
56
+
57
+ export function blockRef<const T extends readonly ComponentContract[]>(
58
+ contracts: T,
59
+ ): z.ZodType<BlockRefUnion<T>> {
60
+ return attachMeta(
61
+ blockRefBase as unknown as z.ZodType<BlockRefUnion<T>>,
62
+ contracts.map((c) => c.id),
63
+ false,
64
+ );
65
+ }
66
+
67
+ export function getBlockRefMeta(schema: unknown): BlockRefMeta | null {
68
+ if (schema && typeof schema === 'object' && BLOCK_REF_KEY in schema) {
69
+ return (schema as Record<string, unknown>)[BLOCK_REF_KEY] as BlockRefMeta;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ export type BlockRef = z.infer<typeof blockRefBase>;
@@ -1,28 +1,47 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ // ---------------------------------------------------------------------------
4
+ // Image source variant (responsive derivative)
5
+ //
6
+ // Each entry is one concrete derivative produced by the compiler for the asset
7
+ // slot. Renderers pick the primary variant and build a srcset from the list.
8
+ // ---------------------------------------------------------------------------
9
+
3
10
  export interface ImageSource {
11
+ /** Derivative URL. */
4
12
  src: string;
13
+ /** Intrinsic width in pixels. */
5
14
  width: number;
15
+ /** Intrinsic height in pixels. */
6
16
  height: number;
17
+ /** MIME type hint, e.g. image/webp. */
7
18
  type: string;
8
19
  }
9
20
 
10
- export interface PictureSource {
11
- media?: string;
12
- type: string;
13
- srcset: string;
14
- sizes?: string;
15
- }
21
+ // ---------------------------------------------------------------------------
22
+ // Image — DAM-backed image asset with compiler-resolved derivatives
23
+ //
24
+ // Content authors write an asset reference: { "$type": "dam.assetRef", ... }
25
+ // The compiler resolves it to this Image value based on the asset's media type.
26
+ // ---------------------------------------------------------------------------
16
27
 
17
- export interface GradialImage {
28
+ export interface Image {
29
+ /** Discriminator — always 'gradial.image'. */
18
30
  $type: 'gradial.image';
31
+ /** DAM asset identifier. */
19
32
  assetId: string;
33
+ /** DAM version identifier. */
20
34
  versionId: string;
35
+ /** Accessible description of the image content. */
21
36
  alt: string;
22
- fallback: ImageSource;
23
- sources: PictureSource[] | null;
37
+ /** Responsive derivative variants produced for the asset slot. */
38
+ sources: ImageSource[];
24
39
  }
25
40
 
41
+ // ---------------------------------------------------------------------------
42
+ // Image slot contract (declared by components that accept image props)
43
+ // ---------------------------------------------------------------------------
44
+
26
45
  export interface ImageSlotContract {
27
46
  outputs: SlotOutput[];
28
47
  formats: string[];
@@ -35,66 +54,66 @@ export interface SlotOutput {
35
54
  media?: string;
36
55
  }
37
56
 
38
- export const GradialImageSchema = z.object({
57
+ // ---------------------------------------------------------------------------
58
+ // Zod schema
59
+ // ---------------------------------------------------------------------------
60
+
61
+ export const ImageSourceSchema = z.object({
62
+ src: z.string().min(1),
63
+ width: z.number().int().nonnegative(),
64
+ height: z.number().int().nonnegative(),
65
+ type: z.string().min(1),
66
+ });
67
+
68
+ export const ImageSchema = z.object({
39
69
  $type: z.literal('gradial.image'),
40
70
  assetId: z.string().min(1),
41
71
  versionId: z.string().min(1),
42
72
  alt: z.string(),
43
- fallback: z.object({
44
- src: z.string().min(1),
45
- width: z.number().int().nonnegative(),
46
- height: z.number().int().nonnegative(),
47
- type: z.string().min(1),
48
- }),
49
- sources: z.array(z.object({
50
- media: z.string().optional(),
51
- type: z.string().min(1),
52
- srcset: z.string().min(1),
53
- sizes: z.string().optional(),
54
- })).nullable().default([]),
73
+ sources: z.array(ImageSourceSchema).min(1),
55
74
  });
56
75
 
57
- export type ImageHTMLAttributes = Record<string, string | number | boolean | undefined | null>;
76
+ // ---------------------------------------------------------------------------
77
+ // Shared attrs helper — consumed by React, Astro, and Svelte components
78
+ // ---------------------------------------------------------------------------
58
79
 
59
- export function renderImageHTML(image: GradialImage, attrs: ImageHTMLAttributes = {}): string {
60
- const imgAttrs = renderAttrs({
61
- ...attrs,
62
- src: image.fallback.src,
63
- alt: image.alt,
64
- width: image.fallback.width > 0 ? image.fallback.width : undefined,
65
- height: image.fallback.height > 0 ? image.fallback.height : undefined,
66
- });
67
- const img = `<img${imgAttrs}>`;
68
- if (!image.sources?.length) {
69
- return img;
70
- }
71
- const sources = image.sources.map((source) => `<source${renderAttrs({
72
- media: source.media,
73
- type: source.type,
74
- srcset: source.srcset,
75
- sizes: source.sizes,
76
- })}>`).join('');
77
- return `<picture>${sources}${img}</picture>`;
78
- }
80
+ export type ImageAttrs = Record<string, unknown>;
79
81
 
80
- function renderAttrs(attrs: ImageHTMLAttributes): string {
81
- const rendered = Object.entries(attrs)
82
- .filter(([, value]) => value !== undefined && value !== null && value !== false)
83
- .map(([name, value]) => value === true ? escapeName(name) : `${escapeName(name)}="${escapeAttr(String(value))}"`);
84
- if (!rendered.length) {
85
- return '';
86
- }
87
- return ' ' + rendered.join(' ');
82
+ /**
83
+ * Picks the primary variant from an image's sources.
84
+ * Uses the largest width; ties fall back to the first entry.
85
+ */
86
+ export function primaryImageSource(image: Image): ImageSource {
87
+ return image.sources.reduce((largest, source) =>
88
+ source.width > largest.width ? source : largest,
89
+ image.sources[0],
90
+ );
88
91
  }
89
92
 
90
- function escapeName(name: string): string {
91
- return name.replace(/[^A-Za-z0-9_:-]/g, '');
92
- }
93
+ /**
94
+ * Computes the full set of <img> attributes for an Image.
95
+ *
96
+ * Framework components (React <Picture>, Astro <img>, Svelte <img>) call this
97
+ * and spread the result into their native element. No HTML string serialization.
98
+ */
99
+ export function imageAttrs(image: Image, overrides: ImageAttrs = {}): ImageAttrs {
100
+ const primary = primaryImageSource(image);
101
+ const srcset = image.sources
102
+ .map((s) => `${s.src} ${s.width}w`)
103
+ .join(', ');
104
+ const sizes = image.sources
105
+ .map((s) => `(max-width: ${s.width}px) ${s.width}px`)
106
+ .join(', ');
93
107
 
94
- function escapeAttr(value: string): string {
95
- return value
96
- .replace(/&/g, '&amp;')
97
- .replace(/"/g, '&quot;')
98
- .replace(/</g, '&lt;')
99
- .replace(/>/g, '&gt;');
108
+ return {
109
+ src: primary.src,
110
+ alt: image.alt,
111
+ width: primary.width > 0 ? primary.width : undefined,
112
+ height: primary.height > 0 ? primary.height : undefined,
113
+ srcSet: srcset || undefined,
114
+ sizes: sizes || undefined,
115
+ loading: 'lazy',
116
+ decoding: 'async',
117
+ ...overrides,
118
+ };
100
119
  }
@@ -0,0 +1,28 @@
1
+ version: "1"
2
+ siteId: "site_astro"
3
+ domain: "meridian.local"
4
+ framework: astro
5
+
6
+ source:
7
+ root: "./"
8
+ outDir: "dist"
9
+ publicDir: "public"
10
+
11
+ componentRegistry: ./src/cms/contracts/components/index.ts
12
+ layoutRegistry: ./src/cms/contracts/layouts/index.ts
13
+
14
+ capabilities:
15
+ staticRender: true
16
+ ssr: true
17
+ ssrIslands: true
18
+ clientIslands: true
19
+ fragmentRender: true
20
+
21
+ routes:
22
+ cmsManaged: "/[...slug]"
23
+ frameworkOwned:
24
+ - "/api/*"
25
+ - "/_astro/*"
26
+ - "/storybook/*"
27
+
28
+ rendererProtocol: stdio-json
@@ -0,0 +1,79 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import tailwindcss from '@tailwindcss/postcss';
4
+
5
+ const sharedRoot = fileURLToPath(new URL('../src', import.meta.url));
6
+
7
+ function withSharedAlias(alias: any) {
8
+ if (Array.isArray(alias)) {
9
+ return [...alias, { find: '@shared', replacement: sharedRoot }];
10
+ }
11
+
12
+ return {
13
+ ...alias,
14
+ '@shared': sharedRoot
15
+ };
16
+ }
17
+
18
+ const config = {
19
+ stories: ['../src/**/*.stories.@(ts|mdx)'],
20
+ addons: ['@storybook/addon-docs'],
21
+ framework: {
22
+ name: '@storybook-astro/framework',
23
+ options: {}
24
+ },
25
+ core: {
26
+ builder: '@storybook/builder-vite'
27
+ },
28
+ staticDirs: ['../public'],
29
+ viteFinal: async (config: any) => {
30
+ const astroComponentPaths = new Set<string>();
31
+ const buildServerPlugin = config.plugins?.flat().find(
32
+ (p: any) => p?.name === 'storybook-astro:build-server'
33
+ );
34
+
35
+ if (buildServerPlugin) {
36
+ const origWriteBundle = buildServerPlugin.writeBundle;
37
+ buildServerPlugin.writeBundle = async function(this: any, ...args: any[]) {
38
+ for (const componentPath of astroComponentPaths) {
39
+ buildServerPlugin.resolveId?.call(
40
+ this,
41
+ componentPath,
42
+ componentPath
43
+ );
44
+ }
45
+ return origWriteBundle?.apply(this, args);
46
+ };
47
+ }
48
+
49
+ config.plugins = [
50
+ {
51
+ name: 'storybook-astro:resolve-compat',
52
+ enforce: 'pre' as const,
53
+ resolveId(id: string, importer: string | undefined) {
54
+ if (id?.endsWith('.astro') && importer) {
55
+ astroComponentPaths.add(resolve(dirname(importer), id));
56
+ }
57
+ return undefined;
58
+ }
59
+ },
60
+ ...config.plugins
61
+ ];
62
+
63
+ return {
64
+ ...config,
65
+ resolve: {
66
+ ...config.resolve,
67
+ alias: withSharedAlias(config.resolve?.alias)
68
+ },
69
+ css: {
70
+ ...config.css,
71
+ postcss: {
72
+ plugins: [tailwindcss()]
73
+ }
74
+ }
75
+ };
76
+ }
77
+ };
78
+
79
+ export default config;
@@ -0,0 +1,6 @@
1
+ import { addons } from 'storybook/manager-api';
2
+ import { themes } from 'storybook/theming';
3
+
4
+ addons.setConfig({
5
+ theme: themes.dark
6
+ });
@@ -0,0 +1,33 @@
1
+ import '../src/design-system/styles.css';
2
+
3
+ const preview = {
4
+ globalTypes: {
5
+ theme: {
6
+ description: 'Theme for components',
7
+ toolbar: {
8
+ title: 'Theme',
9
+ icon: 'sun',
10
+ items: ['light', 'dark'],
11
+ dynamicTitle: true
12
+ }
13
+ }
14
+ },
15
+ decorators: [
16
+ (storyFn: any, context: any) => {
17
+ const theme = context.globals.theme || 'light';
18
+ document.body.setAttribute('data-visual-root', '');
19
+ document.body.className = `theme-${theme} min-h-screen bg-[var(--color-surface-default)]`;
20
+ return storyFn();
21
+ }
22
+ ],
23
+ parameters: {
24
+ layout: 'fullscreen',
25
+ options: {
26
+ storySort: {
27
+ order: ['Core Atomic Blocks', 'Composition', '*']
28
+ }
29
+ }
30
+ }
31
+ };
32
+
33
+ export default preview;
@@ -0,0 +1,37 @@
1
+ import path from 'node:path';
2
+ import { defineConfig } from 'astro/config';
3
+ import node from '@astrojs/node';
4
+ import tailwindcss from '@tailwindcss/postcss';
5
+ import { withAci } from '@gradial/aci/astro';
6
+
7
+ const pageRuntimeBuild = process.env.ACI_CODE_ARTIFACT_BUILD === '1';
8
+ const rawBasePath = String(process.env.ACI_SITE_BASE_PATH || '').trim().replace(/\/+$/g, '');
9
+ const base = rawBasePath
10
+ ? rawBasePath.startsWith('/') ? rawBasePath : `/${rawBasePath}`
11
+ : '/';
12
+ const rawOutDir = String(process.env.ACI_ASTRO_OUT_DIR || '').trim();
13
+ const rawCacheDir = String(process.env.ACI_ASTRO_CACHE_DIR || '').trim();
14
+ const outDir = rawOutDir
15
+ ? (path.isAbsolute(rawOutDir) ? rawOutDir : `./${rawOutDir}`)
16
+ : 'dist';
17
+
18
+ export default defineConfig({
19
+ adapter: pageRuntimeBuild ? node({ mode: 'middleware' }) : undefined,
20
+ integrations: [withAci()],
21
+ srcDir: './src',
22
+ outDir,
23
+ cacheDir: rawCacheDir
24
+ ? (path.isAbsolute(rawCacheDir) ? rawCacheDir : `./${rawCacheDir}`)
25
+ : undefined,
26
+ output: pageRuntimeBuild ? 'server' : 'static',
27
+ trailingSlash: 'always',
28
+ base,
29
+ devToolbar: { enabled: false },
30
+ vite: {
31
+ css: {
32
+ postcss: {
33
+ plugins: [tailwindcss()],
34
+ },
35
+ },
36
+ },
37
+ });
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "{{siteName}}",
3
+ "version": "{{version}}",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "astro dev --host 0.0.0.0",
8
+ "build": "astro build",
9
+ "preview": "ACI_CONTENT_ROOT=./.aci/compiled astro preview --host 0.0.0.0",
10
+ "typecheck": "ACI_CONTENT_ROOT=./.aci/compiled astro check --tsconfig ./tsconfig.json",
11
+ "aci:compile": "node ./node_modules/@gradial/aci/bin/aci.js build --compile-only",
12
+ "content:compile": "node ./node_modules/@gradial/aci/bin/aci.js build --skip-code --content ./.content --out ./.aci/compiled",
13
+ "aci:build": "node ./node_modules/@gradial/aci/bin/aci.js build --content ./.content",
14
+ "aci:doctor": "node ./node_modules/@gradial/aci/bin/aci.js doctor",
15
+ "aci:validate": "node ./node_modules/@gradial/aci/bin/aci.js build --validate-only",
16
+ "storybook": "storybook dev -p 6006",
17
+ "build:storybook": "storybook build"
18
+ },
19
+ "dependencies": {
20
+ "@gradial/aci": "^0.1.20",
21
+ "js-yaml": "^4.1.0",
22
+ "marked": "^18.0.3",
23
+ "zod": "^4.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@astrojs/check": "^0.9.8",
27
+ "@astrojs/node": "^10.1.4",
28
+ "@tailwindcss/postcss": "^4.2.4",
29
+ "@types/js-yaml": "^4.0.9",
30
+ "@types/node": "^25.8.0",
31
+ "astro": "^6.1.9",
32
+ "tailwindcss": "^4.2.4",
33
+ "tsx": "^4.20.0",
34
+ "typescript": "5.9.3",
35
+ "vite": "^7.2.7",
36
+ "storybook": "^10.0.0",
37
+ "@storybook-astro/framework": "^1.6.0",
38
+ "@storybook/addon-docs": "^10.0.0",
39
+ "@storybook/builder-vite": "^10.0.0"
40
+ }
41
+ }
@@ -0,0 +1,18 @@
1
+ import { defineLayoutContract, slot, fragmentDefault } from '@gradial/aci';
2
+
3
+ const layoutContracts = [
4
+ defineLayoutContract({
5
+ name: 'default',
6
+ slots: [
7
+ slot('header', false),
8
+ slot('main', true),
9
+ slot('footer', false),
10
+ ],
11
+ defaults: {
12
+ header: [fragmentDefault('navbar')],
13
+ footer: [fragmentDefault('site-footer')],
14
+ },
15
+ }),
16
+ ];
17
+
18
+ export default layoutContracts;
@@ -0,0 +1,28 @@
1
+ import { createRegistry } from '@gradial/aci';
2
+ import { homeHeroContract } from './contracts/components/homeHero.contract';
3
+ import { containerContract } from './contracts/components/container.contract';
4
+ import { buttonContract } from './contracts/components/button.contract';
5
+ import { siteNavigationContract } from './contracts/components/siteNavigation.contract';
6
+ import { siteFooterContract } from './contracts/components/siteFooter.contract';
7
+ import { featureGridContract } from './contracts/components/featureGrid.contract';
8
+ import { featureCardContract } from './contracts/components/featureCard.contract';
9
+ import { statsBarContract } from './contracts/components/statsBar.contract';
10
+ import HomeHero from '../components/sections/HomeHero.astro';
11
+ import ContainerSection from '../components/sections/ContainerSection.astro';
12
+ import ButtonBlock from '../components/elements/ButtonBlock.astro';
13
+ import Header from '../components/chrome/Header.astro';
14
+ import Footer from '../components/chrome/Footer.astro';
15
+ import FeatureGrid from '../components/sections/FeatureGrid.astro';
16
+ import FeatureCard from '../components/sections/FeatureCard.astro';
17
+ import StatsBar from '../components/sections/StatsBar.astro';
18
+
19
+ export const registry = createRegistry([
20
+ [homeHeroContract, HomeHero],
21
+ [containerContract, ContainerSection],
22
+ [buttonContract, ButtonBlock],
23
+ [siteNavigationContract, Header],
24
+ [siteFooterContract, Footer],
25
+ [featureGridContract, FeatureGrid],
26
+ [featureCardContract, FeatureCard],
27
+ [statsBarContract, StatsBar],
28
+ ]);
@@ -0,0 +1,107 @@
1
+ ---
2
+ import Container from '../elements/Container.astro';
3
+ import Logo from '../elements/Logo.astro';
4
+
5
+ interface FooterLink {
6
+ id: string;
7
+ label: string;
8
+ href: string;
9
+ }
10
+
11
+ interface FooterGroup {
12
+ id: string;
13
+ title: string;
14
+ links: FooterLink[];
15
+ }
16
+
17
+ interface FooterColumn {
18
+ id: string;
19
+ title?: string;
20
+ groups?: FooterGroup[];
21
+ links?: FooterLink[];
22
+ }
23
+
24
+ interface Props {
25
+ brandHref: string;
26
+ brandLabel?: string;
27
+ brandLogo?: { src: string; alt?: string } | null;
28
+ social?: Array<FooterLink & { icon?: string }>;
29
+ columns?: FooterColumn[];
30
+ legalLinks?: FooterLink[];
31
+ copyright?: string;
32
+ }
33
+
34
+ const {
35
+ brandHref,
36
+ brandLabel = 'Gradial',
37
+ brandLogo = null,
38
+ social = [],
39
+ columns = [],
40
+ legalLinks = [],
41
+ copyright = ''
42
+ } = Astro.props;
43
+
44
+ function columnGroups(column: FooterColumn): FooterGroup[] {
45
+ if (column.groups?.length) {
46
+ return column.groups;
47
+ }
48
+
49
+ return [{
50
+ id: column.id,
51
+ title: column.title || '',
52
+ links: column.links || []
53
+ }];
54
+ }
55
+ ---
56
+
57
+ <footer class="bm-footer [font-family:var(--font-family-base)] text-fg" id="footer">
58
+ <Container className="py-[var(--spacing-footer-y)]">
59
+ <div class="grid gap-12 pb-24 md:grid-cols-[var(--footer-grid-columns)] lg:gap-24">
60
+ <a href={brandHref} class="self-start">
61
+ <Logo
62
+ label={brandLabel}
63
+ src={brandLogo?.src || ''}
64
+ alt={brandLogo?.alt || brandLabel}
65
+ className="w-[var(--width-logo-footer-mobile)] md:w-[var(--width-logo-footer)]"
66
+ />
67
+ </a>
68
+ <div class="grid gap-10 sm:grid-cols-2 lg:grid-cols-4">
69
+ {columns.map((column) => (
70
+ <div class="space-y-10">
71
+ {columnGroups(column).map((group) => (
72
+ <section class="space-y-5" aria-label={group.title}>
73
+ <h2 class="text-[length:var(--text-size-body-small)] font-[var(--font-weight-semibold)] text-fg-muted">{group.title}</h2>
74
+ <ul class="space-y-4 text-[length:var(--text-size-body-small)] font-[var(--font-weight-semibold)] leading-body text-fg">
75
+ {group.links.map((link) => (
76
+ <li><a class="transition hover:text-link-hover" href={link.href}>{link.label}</a></li>
77
+ ))}
78
+ </ul>
79
+ </section>
80
+ ))}
81
+ </div>
82
+ ))}
83
+ <section class="space-y-5" aria-label="Social">
84
+ <h2 class="text-[length:var(--text-size-body-small)] font-[var(--font-weight-semibold)] text-fg-muted">Social</h2>
85
+ <ul class="space-y-4 text-[length:var(--text-size-body-small)] font-[var(--font-weight-semibold)] text-fg">
86
+ {social.map((item) => (
87
+ <li>
88
+ <a class="inline-flex items-center gap-2 transition hover:text-link-hover" href={item.href}>
89
+ <span class="min-w-5 text-[length:var(--text-size-caption)] text-fg-muted">{item.icon || item.label.slice(0, 1)}</span>
90
+ <span>{item.label}</span>
91
+ </a>
92
+ </li>
93
+ ))}
94
+ </ul>
95
+ </section>
96
+ </div>
97
+ </div>
98
+ <div class="flex flex-col gap-6 border-t border-edge pt-8 text-[length:var(--text-size-caption)] font-[var(--font-weight-semibold)] text-fg-muted md:flex-row md:items-center md:justify-between">
99
+ <ul class="flex flex-wrap gap-x-9 gap-y-3 text-fg">
100
+ {legalLinks.map((link) => (
101
+ <li><a class="transition hover:text-link-hover" href={link.href}>{link.label}</a></li>
102
+ ))}
103
+ </ul>
104
+ <p>{copyright}</p>
105
+ </div>
106
+ </Container>
107
+ </footer>