@vojtaholik/create-static-kit 1.0.7 → 2.0.0

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 (71) hide show
  1. package/package.json +6 -37
  2. package/src/index.ts +41 -0
  3. package/template/blocks/feature-grid.block.html +28 -0
  4. package/template/blocks/feature-grid.block.ts +32 -0
  5. package/template/blocks/gen/feature-grid.render.ts +92 -0
  6. package/template/blocks/gen/hero.render.ts +79 -0
  7. package/template/blocks/gen/index.ts +4 -0
  8. package/template/blocks/gen/latest-posts.render.ts +114 -0
  9. package/template/blocks/gen/text-section.render.ts +69 -0
  10. package/template/blocks/hero.block.html +31 -0
  11. package/template/blocks/hero.block.ts +40 -0
  12. package/template/blocks/index.ts +31 -0
  13. package/template/blocks/latest-posts.block.html +38 -0
  14. package/template/blocks/latest-posts.block.ts +45 -0
  15. package/template/blocks/text-section.block.html +18 -0
  16. package/template/blocks/text-section.block.ts +25 -0
  17. package/template/cms-blocks.ts +197 -0
  18. package/template/package.json +21 -0
  19. package/template/public/css/styles.css +589 -0
  20. package/template/public/js/dev-overlay.js +366 -0
  21. package/template/public/js/index.js +35 -0
  22. package/template/public/sprite.svg +6 -0
  23. package/template/public/svg/magic-wand.svg +4 -0
  24. package/template/site/pages/about.page.ts +91 -0
  25. package/template/site/pages/base.html +38 -0
  26. package/template/site/pages/index.page.ts +107 -0
  27. package/template/site/pages/index.ts +16 -0
  28. package/template/static-kit.config.ts +10 -0
  29. package/template/tsconfig.json +23 -0
  30. package/bin/create-static-kit.js +0 -3
  31. package/dist/cli.d.ts +0 -3
  32. package/dist/cli.d.ts.map +0 -1
  33. package/dist/cli.js +0 -488
  34. package/templates/default/.cursor/rules/configuration.mdc +0 -20
  35. package/templates/default/.cursor/rules/figma-integration.mdc +0 -22
  36. package/templates/default/.cursor/rules/html-components.mdc +0 -24
  37. package/templates/default/.cursor/rules/html-pages.mdc +0 -22
  38. package/templates/default/.cursor/rules/inline-tailwind-to-bem-with-apply-directive.mdc +0 -218
  39. package/templates/default/.cursor/rules/project-overview.mdc +0 -30
  40. package/templates/default/.cursor/rules/public-assets.mdc +0 -22
  41. package/templates/default/.cursor/rules/scss-styles.mdc +0 -22
  42. package/templates/default/.cursor/rules/svg-icons.mdc +0 -22
  43. package/templates/default/.cursor/rules/typescript-js.mdc +0 -23
  44. package/templates/default/public/favicon.ico +0 -1
  45. package/templates/default/src/components/button.html +0 -1
  46. package/templates/default/src/components/feature-card.html +0 -8
  47. package/templates/default/src/components/footer.html +0 -5
  48. package/templates/default/src/components/navigation.html +0 -11
  49. package/templates/default/src/icons/ui/star.svg +0 -3
  50. package/templates/default/src/js/index.ts +0 -28
  51. package/templates/default/src/pages/about.html +0 -24
  52. package/templates/default/src/pages/index.html +0 -23
  53. package/templates/default/src/styles/main.scss +0 -210
  54. package/templates/default/tsconfig.json +0 -19
  55. package/templates/minimal/.cursor/rules/configuration.mdc +0 -20
  56. package/templates/minimal/.cursor/rules/figma-integration.mdc +0 -22
  57. package/templates/minimal/.cursor/rules/html-components.mdc +0 -24
  58. package/templates/minimal/.cursor/rules/html-pages.mdc +0 -22
  59. package/templates/minimal/.cursor/rules/inline-tailwind-to-bem-with-apply-directive.mdc +0 -218
  60. package/templates/minimal/.cursor/rules/project-overview.mdc +0 -30
  61. package/templates/minimal/.cursor/rules/public-assets.mdc +0 -22
  62. package/templates/minimal/.cursor/rules/scss-styles.mdc +0 -22
  63. package/templates/minimal/.cursor/rules/svg-icons.mdc +0 -22
  64. package/templates/minimal/.cursor/rules/typescript-js.mdc +0 -23
  65. package/templates/minimal/public/favicon.ico +0 -1
  66. package/templates/minimal/src/components/footer.html +0 -3
  67. package/templates/minimal/src/components/header.html +0 -6
  68. package/templates/minimal/src/js/index.ts +0 -9
  69. package/templates/minimal/src/pages/index.html +0 -15
  70. package/templates/minimal/src/styles/main.scss +0 -77
  71. package/templates/minimal/tsconfig.json +0 -19
package/package.json CHANGED
@@ -1,48 +1,17 @@
1
1
  {
2
2
  "name": "@vojtaholik/create-static-kit",
3
- "version": "1.0.7",
4
- "description": "CLI tool to create new Static Kit projects",
3
+ "version": "2.0.0",
5
4
  "type": "module",
6
5
  "bin": {
7
- "create-static-kit": "./bin/create-static-kit.js"
6
+ "create-static-kit": "./src/index.ts"
8
7
  },
9
- "files": [
10
- "bin/",
11
- "dist/",
12
- "templates/"
13
- ],
14
- "scripts": {
15
- "build": "tsc",
16
- "dev": "tsc --watch",
17
- "clean": "rm -rf dist"
18
- },
19
- "dependencies": {
20
- "@vojtaholik/static-kit-core": "^1.0.0",
21
- "prompts": "^2.4.2",
22
- "kleur": "^4.1.5",
23
- "fast-glob": "^3.3.3"
24
- },
25
- "devDependencies": {
26
- "@types/node": "^24.2.1",
27
- "@types/prompts": "^2.4.9",
28
- "typescript": "~5.9.2"
29
- },
30
- "keywords": [
31
- "create",
32
- "static-kit",
33
- "cli",
34
- "scaffold",
35
- "template"
36
- ],
37
- "author": "Vojta Holik <vojta@holik.dev>",
38
- "license": "MIT",
8
+ "files": ["src", "template"],
39
9
  "repository": {
40
10
  "type": "git",
41
- "url": "https://github.com/vojtaholik/static-kit.git",
11
+ "url": "https://github.com/vojtaholik/module-kit",
42
12
  "directory": "packages/create-static-kit"
43
13
  },
44
- "homepage": "https://github.com/vojtaholik/static-kit#readme",
45
- "bugs": {
46
- "url": "https://github.com/vojtaholik/static-kit/issues"
14
+ "publishConfig": {
15
+ "access": "public"
47
16
  }
48
17
  }
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Create Static Kit - Project Scaffolder
4
+ *
5
+ * Usage:
6
+ * bun create @vojtaholik/static-kit my-site
7
+ */
8
+
9
+ import { cp, mkdir } from "node:fs/promises";
10
+ import { join, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const templateDir = join(__dirname, "..", "template");
16
+
17
+ const targetDir = process.argv[2] || ".";
18
+ const targetPath = join(process.cwd(), targetDir);
19
+
20
+ console.log(`\n🚀 Creating Static Kit project in ${targetPath}\n`);
21
+
22
+ await mkdir(targetPath, { recursive: true });
23
+ await cp(templateDir, targetPath, { recursive: true });
24
+
25
+ if (targetDir !== ".") {
26
+ const packageJsonPath = join(targetPath, "package.json");
27
+ const packageJson = await Bun.file(packageJsonPath).json();
28
+ packageJson.name = targetDir.split("/").pop();
29
+ await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
30
+ }
31
+
32
+ console.log(`✅ Done!
33
+
34
+ Next steps:
35
+ ${targetDir !== "." ? `cd ${targetDir}` : ""}
36
+ bun install
37
+ bun run gen
38
+ bun run dev
39
+
40
+ Happy building! 🎉
41
+ `);
@@ -0,0 +1,28 @@
1
+ <section class="section section--feature-grid section--tone-{{ ctx.layout.tone }}" data-block-id="{{ addr.blockId }}" data-schema-address="{{ encodeSchemaAddress(addr) }}">
2
+ <div class="container container--{{ ctx.layout.contentWidth }}">
3
+ <template v-if="props.headline || props.subheadline">
4
+ <div class="section__header section__header--{{ ctx.layout.contentAlign }}">
5
+ <template v-if="props.headline">
6
+ <h2 class="h2">{{ props.headline }}</h2>
7
+ </template>
8
+ <template v-if="props.subheadline">
9
+ <p class="text-body">{{ props.subheadline }}</p>
10
+ </template>
11
+ </div>
12
+ </template>
13
+ <div class="grid grid--{{ props.columns || '3' }}">
14
+ <template v-for="feature, i in props.features">
15
+ <div class="card">
16
+ <template v-if="feature.icon">
17
+ <span class="card__icon">{{ feature.icon }}</span>
18
+ </template>
19
+ <h3 class="h3 card__title">{{ feature.title }}</h3>
20
+ <p class="text-body card__description">{{ feature.description }}</p>
21
+ <template v-if="feature.link">
22
+ <a class="card__link" :href="feature.link.href">{{ feature.link.label }}</a>
23
+ </template>
24
+ </div>
25
+ </template>
26
+ </div>
27
+ </div>
28
+ </section>
@@ -0,0 +1,32 @@
1
+ import { z } from "zod/v4";
2
+ import { defineBlock } from "@vojtaholik/static-kit-core";
3
+ import { renderFeatureGrid } from "./gen/feature-grid.render.ts";
4
+
5
+ export const featureGridPropsSchema = z.object({
6
+ headline: z.string().optional(),
7
+ subheadline: z.string().optional(),
8
+ columns: z.enum(["2", "3", "4"]).default("3"),
9
+ features: z.array(
10
+ z.object({
11
+ icon: z.string().optional(),
12
+ title: z.string(),
13
+ description: z.string(),
14
+ link: z
15
+ .object({
16
+ href: z.string(),
17
+ label: z.string(),
18
+ external: z.boolean().optional(),
19
+ })
20
+ .optional(),
21
+ })
22
+ ),
23
+ });
24
+
25
+ export type FeatureGridProps = z.infer<typeof featureGridPropsSchema>;
26
+
27
+ export const featureGridBlock = defineBlock({
28
+ type: "featureGrid",
29
+ propsSchema: featureGridPropsSchema,
30
+ renderHtml: renderFeatureGrid,
31
+ sourceFile: import.meta.url,
32
+ });
@@ -0,0 +1,92 @@
1
+ // Auto-generated - DO NOT EDIT
2
+ import { escapeHtml, escapeAttr, type RenderBlockInput } from "@static-block-kit/core";
3
+ import { encodeSchemaAddress } from "@static-block-kit/core";
4
+
5
+ export function renderFeatureGrid(input: RenderBlockInput): string {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ const { props, ctx, addr } = input as { props: any; ctx: typeof input.ctx; addr: typeof input.addr };
8
+ let out = "";
9
+ out += "<section";
10
+ out += " class=\"";
11
+ out += "section section--feature-grid section--tone-";
12
+ out += escapeAttr(ctx.layout.tone);
13
+ out += "\"";
14
+ out += " data-block-id=\"";
15
+ out += escapeAttr(addr.blockId);
16
+ out += "\"";
17
+ out += " data-schema-address=\"";
18
+ out += escapeAttr(encodeSchemaAddress(addr));
19
+ out += "\"";
20
+ out += ">";
21
+ out += "<div";
22
+ out += " class=\"";
23
+ out += "container container--";
24
+ out += escapeAttr(ctx.layout.contentWidth);
25
+ out += "\"";
26
+ out += ">";
27
+ if (props.headline || props.subheadline) {
28
+ out += "<div";
29
+ out += " class=\"";
30
+ out += "section__header section__header--";
31
+ out += escapeAttr(ctx.layout.contentAlign);
32
+ out += "\"";
33
+ out += ">";
34
+ if (props.headline) {
35
+ out += "<h2";
36
+ out += " class=\"h2\"";
37
+ out += ">";
38
+ out += escapeHtml(props.headline);
39
+ out += "</h2>";
40
+ }
41
+ if (props.subheadline) {
42
+ out += "<p";
43
+ out += " class=\"text-body\"";
44
+ out += ">";
45
+ out += escapeHtml(props.subheadline);
46
+ out += "</p>";
47
+ }
48
+ out += "</div>";
49
+ }
50
+ out += "<div";
51
+ out += " class=\"";
52
+ out += "grid grid--";
53
+ out += escapeAttr(props.columns || '3');
54
+ out += "\"";
55
+ out += ">";
56
+ for (const [i, feature] of (props.features).entries()) {
57
+ out += "<div";
58
+ out += " class=\"card\"";
59
+ out += ">";
60
+ if (feature.icon) {
61
+ out += "<span";
62
+ out += " class=\"card__icon\"";
63
+ out += ">";
64
+ out += escapeHtml(feature.icon);
65
+ out += "</span>";
66
+ }
67
+ out += "<h3";
68
+ out += " class=\"h3 card__title\"";
69
+ out += ">";
70
+ out += escapeHtml(feature.title);
71
+ out += "</h3>";
72
+ out += "<p";
73
+ out += " class=\"text-body card__description\"";
74
+ out += ">";
75
+ out += escapeHtml(feature.description);
76
+ out += "</p>";
77
+ if (feature.link) {
78
+ out += "<a";
79
+ out += " class=\"card__link\"";
80
+ out += " href=\"" + escapeAttr(feature.link.href) + "\"";
81
+ out += ">";
82
+ out += escapeHtml(feature.link.label);
83
+ out += "</a>";
84
+ }
85
+ out += "</div>";
86
+ }
87
+ out += "</div>";
88
+ out += "</div>";
89
+ out += "</section>";
90
+
91
+ return out;
92
+ }
@@ -0,0 +1,79 @@
1
+ // Auto-generated - DO NOT EDIT
2
+ import { escapeHtml, escapeAttr, type RenderBlockInput } from "@static-block-kit/core";
3
+ import { encodeSchemaAddress } from "@static-block-kit/core";
4
+
5
+ export function renderHero(input: RenderBlockInput): string {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ const { props, ctx, addr } = input as { props: any; ctx: typeof input.ctx; addr: typeof input.addr };
8
+ let out = "";
9
+ out += "<section";
10
+ out += " class=\"";
11
+ out += "section section--hero section--tone-";
12
+ out += escapeAttr(ctx.layout.tone);
13
+ out += "\"";
14
+ out += " data-block-id=\"";
15
+ out += escapeAttr(addr.blockId);
16
+ out += "\"";
17
+ out += " data-schema-address=\"";
18
+ out += escapeAttr(encodeSchemaAddress(addr));
19
+ out += "\"";
20
+ out += ">";
21
+ out += "<div";
22
+ out += " class=\"";
23
+ out += "container container--";
24
+ out += escapeAttr(ctx.layout.contentWidth);
25
+ out += "\"";
26
+ out += ">";
27
+ out += "<div";
28
+ out += " class=\"";
29
+ out += "hero__content hero__content--";
30
+ out += escapeAttr(ctx.layout.contentAlign);
31
+ out += "\"";
32
+ out += ">";
33
+ if (props.eyebrow) {
34
+ out += "<span";
35
+ out += " class=\"eyebrow\"";
36
+ out += ">";
37
+ out += escapeHtml(props.eyebrow);
38
+ out += "</span>";
39
+ }
40
+ out += "<h1";
41
+ out += " class=\"h1 hero__headline\"";
42
+ out += ">";
43
+ out += escapeHtml(props.headline);
44
+ out += "</h1>";
45
+ if (props.subheadline) {
46
+ out += "<p";
47
+ out += " class=\"text-body hero__subheadline\"";
48
+ out += ">";
49
+ out += escapeHtml(props.subheadline);
50
+ out += "</p>";
51
+ }
52
+ if (props.primaryCta || props.secondaryCta) {
53
+ out += "<div";
54
+ out += " class=\"hero__actions\"";
55
+ out += ">";
56
+ if (props.primaryCta) {
57
+ out += "<a";
58
+ out += " class=\"btn btn--primary\"";
59
+ out += " href=\"" + escapeAttr(props.primaryCta.href) + "\"";
60
+ out += ">";
61
+ out += escapeHtml(props.primaryCta.label);
62
+ out += "</a>";
63
+ }
64
+ if (props.secondaryCta) {
65
+ out += "<a";
66
+ out += " class=\"btn btn--secondary\"";
67
+ out += " href=\"" + escapeAttr(props.secondaryCta.href) + "\"";
68
+ out += ">";
69
+ out += escapeHtml(props.secondaryCta.label);
70
+ out += "</a>";
71
+ }
72
+ out += "</div>";
73
+ }
74
+ out += "</div>";
75
+ out += "</div>";
76
+ out += "</section>";
77
+
78
+ return out;
79
+ }
@@ -0,0 +1,4 @@
1
+ export { renderFeatureGrid } from "./feature-grid.render.ts";
2
+ export { renderTextSection } from "./text-section.render.ts";
3
+ export { renderHero } from "./hero.render.ts";
4
+ export { renderLatestPosts } from "./latest-posts.render.ts";
@@ -0,0 +1,114 @@
1
+ // Auto-generated - DO NOT EDIT
2
+ import { escapeHtml, escapeAttr, type RenderBlockInput } from "@static-block-kit/core";
3
+ import { encodeSchemaAddress } from "@static-block-kit/core";
4
+
5
+ export function renderLatestPosts(input: RenderBlockInput): string {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ const { props, ctx, addr } = input as { props: any; ctx: typeof input.ctx; addr: typeof input.addr };
8
+ let out = "";
9
+ out += "<section";
10
+ out += " class=\"";
11
+ out += "section section--latest-posts section--tone-";
12
+ out += escapeAttr(ctx.layout.tone);
13
+ out += "\"";
14
+ out += " data-block-id=\"";
15
+ out += escapeAttr(addr.blockId);
16
+ out += "\"";
17
+ out += " data-schema-address=\"";
18
+ out += escapeAttr(encodeSchemaAddress(addr));
19
+ out += "\"";
20
+ out += ">";
21
+ out += "<div";
22
+ out += " class=\"";
23
+ out += "container container--";
24
+ out += escapeAttr(ctx.layout.contentWidth);
25
+ out += "\"";
26
+ out += ">";
27
+ out += "<div";
28
+ out += " class=\"";
29
+ out += "section__header section__header--";
30
+ out += escapeAttr(ctx.layout.contentAlign);
31
+ out += "\"";
32
+ out += ">";
33
+ if (props.headline) {
34
+ out += "<h2";
35
+ out += " class=\"h2\"";
36
+ out += ">";
37
+ out += escapeHtml(props.headline);
38
+ out += "</h2>";
39
+ }
40
+ if (props.subheadline) {
41
+ out += "<p";
42
+ out += " class=\"text-body\"";
43
+ out += ">";
44
+ out += escapeHtml(props.subheadline);
45
+ out += "</p>";
46
+ }
47
+ out += "</div>";
48
+ out += "<div";
49
+ out += " class=\"grid grid--3\"";
50
+ out += ">";
51
+ for (const [i, post] of (props.posts).entries()) {
52
+ out += "<article";
53
+ out += " class=\"card card--post\"";
54
+ out += ">";
55
+ if (post.image) {
56
+ out += "<div";
57
+ out += " class=\"card__image\"";
58
+ out += ">";
59
+ out += "<img";
60
+ out += " loading=\"lazy\"";
61
+ out += " src=\"" + escapeAttr(post.image.src) + "\"";
62
+ out += " alt=\"" + escapeAttr(post.image.alt) + "\"";
63
+ out += ">";
64
+ out += "</div>";
65
+ }
66
+ out += "<div";
67
+ out += " class=\"card__body\"";
68
+ out += ">";
69
+ if (post.date) {
70
+ out += "<time";
71
+ out += " class=\"card__date\"";
72
+ out += ">";
73
+ out += escapeHtml(post.date);
74
+ out += "</time>";
75
+ }
76
+ out += "<h3";
77
+ out += " class=\"h3 card__title\"";
78
+ out += ">";
79
+ out += escapeHtml(post.title);
80
+ out += "</h3>";
81
+ if (post.excerpt) {
82
+ out += "<p";
83
+ out += " class=\"text-body card__excerpt\"";
84
+ out += ">";
85
+ out += escapeHtml(post.excerpt);
86
+ out += "</p>";
87
+ }
88
+ out += "<a";
89
+ out += " class=\"card__link\"";
90
+ out += " href=\"" + escapeAttr(post.link.href) + "\"";
91
+ out += ">";
92
+ out += escapeHtml(post.link.label);
93
+ out += "</a>";
94
+ out += "</div>";
95
+ out += "</article>";
96
+ }
97
+ out += "</div>";
98
+ if (props.viewAllLink) {
99
+ out += "<div";
100
+ out += " class=\"section__footer\"";
101
+ out += ">";
102
+ out += "<a";
103
+ out += " class=\"btn btn--secondary\"";
104
+ out += " href=\"" + escapeAttr(props.viewAllLink.href) + "\"";
105
+ out += ">";
106
+ out += escapeHtml(props.viewAllLink.label);
107
+ out += "</a>";
108
+ out += "</div>";
109
+ }
110
+ out += "</div>";
111
+ out += "</section>";
112
+
113
+ return out;
114
+ }
@@ -0,0 +1,69 @@
1
+ // Auto-generated - DO NOT EDIT
2
+ import { escapeHtml, escapeAttr, type RenderBlockInput } from "@static-block-kit/core";
3
+ import { encodeSchemaAddress } from "@static-block-kit/core";
4
+
5
+ export function renderTextSection(input: RenderBlockInput): string {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ const { props, ctx, addr } = input as { props: any; ctx: typeof input.ctx; addr: typeof input.addr };
8
+ let out = "";
9
+ out += "<section";
10
+ out += " class=\"";
11
+ out += "section section--text section--tone-";
12
+ out += escapeAttr(ctx.layout.tone);
13
+ out += "\"";
14
+ out += " data-block-id=\"";
15
+ out += escapeAttr(addr.blockId);
16
+ out += "\"";
17
+ out += " data-schema-address=\"";
18
+ out += escapeAttr(encodeSchemaAddress(addr));
19
+ out += "\"";
20
+ out += ">";
21
+ out += "<div";
22
+ out += " class=\"";
23
+ out += "container container--";
24
+ out += escapeAttr(ctx.layout.contentWidth);
25
+ out += "\"";
26
+ out += ">";
27
+ out += "<div";
28
+ out += " class=\"";
29
+ out += "text-section text-section--";
30
+ out += escapeAttr(ctx.layout.contentAlign);
31
+ out += "\"";
32
+ out += ">";
33
+ if (props.eyebrow) {
34
+ out += "<span";
35
+ out += " class=\"eyebrow\"";
36
+ out += ">";
37
+ out += escapeHtml(props.eyebrow);
38
+ out += "</span>";
39
+ }
40
+ if (props.headline) {
41
+ out += "<h2";
42
+ out += " class=\"h2\"";
43
+ out += ">";
44
+ out += escapeHtml(props.headline);
45
+ out += "</h2>";
46
+ }
47
+ out += "<div";
48
+ out += " class=\"prose\"";
49
+ out += ">";
50
+ out += props.body;
51
+ out += "</div>";
52
+ if (props.cta) {
53
+ out += "<div";
54
+ out += " class=\"text-section__cta\"";
55
+ out += ">";
56
+ out += "<a";
57
+ out += " class=\"btn btn--primary\"";
58
+ out += " href=\"" + escapeAttr(props.cta.href) + "\"";
59
+ out += ">";
60
+ out += escapeHtml(props.cta.label);
61
+ out += "</a>";
62
+ out += "</div>";
63
+ }
64
+ out += "</div>";
65
+ out += "</div>";
66
+ out += "</section>";
67
+
68
+ return out;
69
+ }
@@ -0,0 +1,31 @@
1
+ <section
2
+ class="section section--hero section--tone-{{ ctx.layout.tone }}"
3
+ data-block-id="{{ addr.blockId }}"
4
+ data-schema-address="{{ encodeSchemaAddress(addr) }}"
5
+ >
6
+ <div class="container container--{{ ctx.layout.contentWidth }}">
7
+ <div class="hero__content hero__content--{{ ctx.layout.contentAlign }}">
8
+ <template v-if="props.eyebrow">
9
+ <span class="eyebrow">{{ props.eyebrow }}</span>
10
+ </template>
11
+ <h1 class="h1 hero__headline">{{ props.headline }}</h1>
12
+ <template v-if="props.subheadline">
13
+ <p class="text-body hero__subheadline">{{ props.subheadline }}</p>
14
+ </template>
15
+ <template v-if="props.primaryCta || props.secondaryCta">
16
+ <div class="hero__actions">
17
+ <template v-if="props.primaryCta">
18
+ <a class="btn btn--primary" :href="props.primaryCta.href">
19
+ {{ props.primaryCta.label }}
20
+ </a>
21
+ </template>
22
+ <template v-if="props.secondaryCta">
23
+ <a class="btn btn--secondary" :href="props.secondaryCta.href">
24
+ {{ props.secondaryCta.label }}
25
+ </a>
26
+ </template>
27
+ </div>
28
+ </template>
29
+ </div>
30
+ </div>
31
+ </section>
@@ -0,0 +1,40 @@
1
+ import { z } from "zod/v4";
2
+ import { defineBlock } from "@vojtaholik/static-kit-core";
3
+ import { renderHero } from "./gen/hero.render.ts";
4
+
5
+ export const heroPropsSchema = z.object({
6
+ eyebrow: z.string().optional(),
7
+ headline: z.string(),
8
+ subheadline: z.string().optional(),
9
+ primaryCta: z
10
+ .object({
11
+ href: z.string(),
12
+ label: z.string(),
13
+ external: z.boolean().optional(),
14
+ })
15
+ .optional(),
16
+ secondaryCta: z
17
+ .object({
18
+ href: z.string(),
19
+ label: z.string(),
20
+ external: z.boolean().optional(),
21
+ })
22
+ .optional(),
23
+ backgroundImage: z
24
+ .object({
25
+ src: z.string(),
26
+ alt: z.string(),
27
+ width: z.number().optional(),
28
+ height: z.number().optional(),
29
+ })
30
+ .optional(),
31
+ });
32
+
33
+ export type HeroProps = z.infer<typeof heroPropsSchema>;
34
+
35
+ export const heroBlock = defineBlock({
36
+ type: "hero",
37
+ propsSchema: heroPropsSchema,
38
+ renderHtml: renderHero,
39
+ sourceFile: import.meta.url,
40
+ });
@@ -0,0 +1,31 @@
1
+ // Block exports
2
+ export { heroBlock, heroPropsSchema, type HeroProps } from "./hero.block.ts";
3
+ export {
4
+ featureGridBlock,
5
+ featureGridPropsSchema,
6
+ type FeatureGridProps,
7
+ } from "./feature-grid.block.ts";
8
+ export {
9
+ latestPostsBlock,
10
+ latestPostsPropsSchema,
11
+ type LatestPostsProps,
12
+ } from "./latest-posts.block.ts";
13
+ export {
14
+ textSectionBlock,
15
+ textSectionPropsSchema,
16
+ type TextSectionProps,
17
+ } from "./text-section.block.ts";
18
+
19
+ // Register all blocks
20
+ import { blockRegistry } from "@vojtaholik/static-kit-core";
21
+ import { heroBlock } from "./hero.block.ts";
22
+ import { featureGridBlock } from "./feature-grid.block.ts";
23
+ import { latestPostsBlock } from "./latest-posts.block.ts";
24
+ import { textSectionBlock } from "./text-section.block.ts";
25
+
26
+ export function registerAllBlocks() {
27
+ blockRegistry.register(heroBlock);
28
+ blockRegistry.register(featureGridBlock);
29
+ blockRegistry.register(latestPostsBlock);
30
+ blockRegistry.register(textSectionBlock);
31
+ }
@@ -0,0 +1,38 @@
1
+ <section class="section section--latest-posts section--tone-{{ ctx.layout.tone }}" data-block-id="{{ addr.blockId }}" data-schema-address="{{ encodeSchemaAddress(addr) }}">
2
+ <div class="container container--{{ ctx.layout.contentWidth }}">
3
+ <div class="section__header section__header--{{ ctx.layout.contentAlign }}">
4
+ <template v-if="props.headline">
5
+ <h2 class="h2">{{ props.headline }}</h2>
6
+ </template>
7
+ <template v-if="props.subheadline">
8
+ <p class="text-body">{{ props.subheadline }}</p>
9
+ </template>
10
+ </div>
11
+ <div class="grid grid--3">
12
+ <template v-for="post, i in props.posts">
13
+ <article class="card card--post">
14
+ <template v-if="post.image">
15
+ <div class="card__image">
16
+ <img :src="post.image.src" :alt="post.image.alt" loading="lazy">
17
+ </div>
18
+ </template>
19
+ <div class="card__body">
20
+ <template v-if="post.date">
21
+ <time class="card__date">{{ post.date }}</time>
22
+ </template>
23
+ <h3 class="h3 card__title">{{ post.title }}</h3>
24
+ <template v-if="post.excerpt">
25
+ <p class="text-body card__excerpt">{{ post.excerpt }}</p>
26
+ </template>
27
+ <a class="card__link" :href="post.link.href">{{ post.link.label }}</a>
28
+ </div>
29
+ </article>
30
+ </template>
31
+ </div>
32
+ <template v-if="props.viewAllLink">
33
+ <div class="section__footer">
34
+ <a class="btn btn--secondary" :href="props.viewAllLink.href">{{ props.viewAllLink.label }}</a>
35
+ </div>
36
+ </template>
37
+ </div>
38
+ </section>