@avocadostudio-ai/blocks 0.1.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 (88) hide show
  1. package/LICENSE +201 -0
  2. package/dist/block-error-boundary.d.ts +18 -0
  3. package/dist/block-error-boundary.js +36 -0
  4. package/dist/blocks/_base.css +108 -0
  5. package/dist/blocks/_layout.css +47 -0
  6. package/dist/blocks/_shared.d.ts +24 -0
  7. package/dist/blocks/_shared.js +153 -0
  8. package/dist/blocks/_tokens.css +127 -0
  9. package/dist/blocks/banner/renderer.d.ts +2 -0
  10. package/dist/blocks/banner/renderer.js +21 -0
  11. package/dist/blocks/banner/styles.css +72 -0
  12. package/dist/blocks/block-image.d.ts +14 -0
  13. package/dist/blocks/block-image.js +10 -0
  14. package/dist/blocks/block-types.d.ts +2 -0
  15. package/dist/blocks/block-types.js +22 -0
  16. package/dist/blocks/card/renderer.d.ts +1 -0
  17. package/dist/blocks/card/renderer.js +11 -0
  18. package/dist/blocks/card/styles.css +94 -0
  19. package/dist/blocks/card-grid/renderer.d.ts +1 -0
  20. package/dist/blocks/card-grid/renderer.js +17 -0
  21. package/dist/blocks/card-grid/styles.css +38 -0
  22. package/dist/blocks/carousel/init.d.ts +8 -0
  23. package/dist/blocks/carousel/init.js +35 -0
  24. package/dist/blocks/carousel/renderer.d.ts +2 -0
  25. package/dist/blocks/carousel/renderer.js +19 -0
  26. package/dist/blocks/carousel/styles.css +136 -0
  27. package/dist/blocks/cta/renderer.d.ts +1 -0
  28. package/dist/blocks/cta/renderer.js +7 -0
  29. package/dist/blocks/cta/styles.css +111 -0
  30. package/dist/blocks/embed/renderer.d.ts +2 -0
  31. package/dist/blocks/embed/renderer.js +16 -0
  32. package/dist/blocks/embed/styles.css +58 -0
  33. package/dist/blocks/faq-accordion/renderer.d.ts +1 -0
  34. package/dist/blocks/faq-accordion/renderer.js +11 -0
  35. package/dist/blocks/faq-accordion/styles.css +68 -0
  36. package/dist/blocks/feature-grid/renderer.d.ts +1 -0
  37. package/dist/blocks/feature-grid/renderer.js +12 -0
  38. package/dist/blocks/feature-grid/styles.css +62 -0
  39. package/dist/blocks/footer/renderer.d.ts +1 -0
  40. package/dist/blocks/footer/renderer.js +19 -0
  41. package/dist/blocks/footer/styles.css +64 -0
  42. package/dist/blocks/gallery/renderer.d.ts +2 -0
  43. package/dist/blocks/gallery/renderer.js +21 -0
  44. package/dist/blocks/gallery/styles.css +59 -0
  45. package/dist/blocks/hero/renderer.d.ts +1 -0
  46. package/dist/blocks/hero/renderer.js +20 -0
  47. package/dist/blocks/hero/styles.css +137 -0
  48. package/dist/blocks/index.d.ts +5 -0
  49. package/dist/blocks/index.js +47 -0
  50. package/dist/blocks/quote/renderer.d.ts +2 -0
  51. package/dist/blocks/quote/renderer.js +9 -0
  52. package/dist/blocks/quote/styles.css +97 -0
  53. package/dist/blocks/rich-text/renderer.d.ts +1 -0
  54. package/dist/blocks/rich-text/renderer.js +15 -0
  55. package/dist/blocks/rich-text/styles.css +64 -0
  56. package/dist/blocks/site-header/renderer.d.ts +1 -0
  57. package/dist/blocks/site-header/renderer.js +64 -0
  58. package/dist/blocks/site-header/styles.css +345 -0
  59. package/dist/blocks/stats/renderer.d.ts +1 -0
  60. package/dist/blocks/stats/renderer.js +13 -0
  61. package/dist/blocks/stats/styles.css +64 -0
  62. package/dist/blocks/styles.css +23 -0
  63. package/dist/blocks/table/renderer.d.ts +2 -0
  64. package/dist/blocks/table/renderer.js +14 -0
  65. package/dist/blocks/table/styles.css +87 -0
  66. package/dist/blocks/tabs/init.d.ts +5 -0
  67. package/dist/blocks/tabs/init.js +38 -0
  68. package/dist/blocks/tabs/renderer.d.ts +2 -0
  69. package/dist/blocks/tabs/renderer.js +30 -0
  70. package/dist/blocks/tabs/styles.css +147 -0
  71. package/dist/blocks/testimonials/renderer.d.ts +1 -0
  72. package/dist/blocks/testimonials/renderer.js +15 -0
  73. package/dist/blocks/testimonials/styles.css +92 -0
  74. package/dist/blocks/two-column/renderer.d.ts +1 -0
  75. package/dist/blocks/two-column/renderer.js +41 -0
  76. package/dist/blocks/two-column/styles.css +83 -0
  77. package/dist/blocks/video/renderer.d.ts +2 -0
  78. package/dist/blocks/video/renderer.js +52 -0
  79. package/dist/blocks/video/styles.css +84 -0
  80. package/dist/blocks-hydrator.d.ts +5 -0
  81. package/dist/blocks-hydrator.js +15 -0
  82. package/dist/catalogue.d.ts +1 -0
  83. package/dist/catalogue.js +741 -0
  84. package/dist/index.d.ts +7 -0
  85. package/dist/index.js +5 -0
  86. package/dist/renderer.d.ts +12 -0
  87. package/dist/renderer.js +35 -0
  88. package/package.json +54 -0
@@ -0,0 +1,147 @@
1
+ .tabs-block {
2
+ padding: 40px 20px;
3
+ background: var(--section-bg);
4
+ }
5
+
6
+ .tabs-block__title {
7
+ margin: 0 0 20px;
8
+ }
9
+
10
+ /* Tab bar wrapper — provides fade gradient overflow hints */
11
+ .tabs-block__bar-wrap {
12
+ position: relative;
13
+ margin-bottom: 24px;
14
+ }
15
+
16
+ .tabs-block__bar-wrap::after {
17
+ content: "";
18
+ position: absolute;
19
+ top: 0;
20
+ right: 0;
21
+ bottom: 2px; /* above the border-bottom */
22
+ width: 48px;
23
+ pointer-events: none;
24
+ background: linear-gradient(to right, transparent, var(--section-bg, var(--bg-0)));
25
+ opacity: 0;
26
+ transition: opacity 0.15s;
27
+ }
28
+
29
+ /* Show fade when bar is scrollable — JS adds this class */
30
+ .tabs-block__bar-wrap--overflow::after {
31
+ opacity: 1;
32
+ }
33
+
34
+ /* Hide fade when scrolled to the end */
35
+ .tabs-block__bar-wrap--at-end::after {
36
+ opacity: 0;
37
+ }
38
+
39
+ /* Tab bar */
40
+ .tabs-block__bar {
41
+ display: flex;
42
+ gap: 0;
43
+ border-bottom: 2px solid var(--border);
44
+ overflow-x: auto;
45
+ scrollbar-width: none;
46
+ }
47
+
48
+ .tabs-block__bar::-webkit-scrollbar {
49
+ display: none;
50
+ }
51
+
52
+ .tabs-block__tab {
53
+ padding: 10px 20px;
54
+ border: none;
55
+ background: none;
56
+ font-size: 1rem;
57
+ font-weight: 500;
58
+ color: var(--text-200);
59
+ cursor: pointer;
60
+ white-space: nowrap;
61
+ transition: color 0.15s, box-shadow 0.15s;
62
+ }
63
+
64
+ .tabs-block__tab:hover {
65
+ color: var(--heading);
66
+ }
67
+
68
+ .tabs-block__tab--active {
69
+ color: var(--brand);
70
+ box-shadow: inset 0 -2px 0 var(--brand);
71
+ }
72
+
73
+ /* Panel content */
74
+ .tabs-block__content {
75
+ font-size: 1rem;
76
+ line-height: 1.6;
77
+ color: var(--text-100);
78
+ }
79
+
80
+ .tabs-block__content p {
81
+ margin: 0 0 12px;
82
+ }
83
+
84
+ .tabs-block__content p:last-child {
85
+ margin-bottom: 0;
86
+ }
87
+
88
+ .tabs-block__content h3 {
89
+ margin: 0 0 8px;
90
+ font-size: 1.15rem;
91
+ font-weight: 600;
92
+ color: var(--heading);
93
+ }
94
+
95
+ .tabs-block__content ul {
96
+ margin: 0 0 12px;
97
+ padding-left: 24px;
98
+ list-style: none;
99
+ }
100
+
101
+ .tabs-block__content ol {
102
+ margin: 0 0 12px;
103
+ padding-left: 24px;
104
+ list-style: none;
105
+ counter-reset: ol-counter;
106
+ }
107
+
108
+ .tabs-block__content ul li::before {
109
+ content: "•";
110
+ color: var(--brand);
111
+ font-weight: 700;
112
+ display: inline-block;
113
+ width: 1em;
114
+ margin-left: -1em;
115
+ }
116
+
117
+ .tabs-block__content ol li {
118
+ counter-increment: ol-counter;
119
+ }
120
+
121
+ .tabs-block__content ol li::before {
122
+ content: counter(ol-counter) ".";
123
+ color: var(--brand);
124
+ font-weight: 600;
125
+ display: inline-block;
126
+ width: 1.5em;
127
+ margin-left: -1.5em;
128
+ }
129
+
130
+ .tabs-block__content li {
131
+ margin-bottom: 4px;
132
+ }
133
+
134
+ @media (max-width: 600px) {
135
+ .tabs-block {
136
+ padding: 24px 12px;
137
+ }
138
+
139
+ .tabs-block__tab {
140
+ padding: 8px 14px;
141
+ font-size: 0.95rem;
142
+ }
143
+
144
+ .tabs-block__bar {
145
+ margin-bottom: 16px;
146
+ }
147
+ }
@@ -0,0 +1 @@
1
+ export declare function Testimonials(props: Record<string, unknown>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolveHeadingTag } from "@avocadostudio-ai/shared";
3
+ import { BlockImage, renderBlockIcon } from "../_shared.js";
4
+ export function Testimonials(props) {
5
+ const items = Array.isArray(props.items) ? props.items : [];
6
+ const HeadingTag = resolveHeadingTag("Testimonials", props);
7
+ return (_jsx("section", { children: _jsxs("div", { className: "section__inner", children: [_jsx(HeadingTag, { "data-editable-target": "title", "data-editable-target-label": "title", "data-editable-label": "title", children: String(props.title ?? "") }), _jsx("div", { className: "testimonials-grid", children: items.map((item, idx) => {
8
+ const row = (item ?? {});
9
+ const imageUrl = typeof row.imageUrl === "string" ? row.imageUrl.trim() : "";
10
+ const imageAlt = typeof row.imageAlt === "string" ? row.imageAlt.trim() : "";
11
+ const role = typeof row.role === "string" ? row.role.trim() : "";
12
+ const iconNode = renderBlockIcon(row.icon, { className: "testimonial-card__icon", size: 32 });
13
+ return (_jsxs("blockquote", { className: "testimonial-card", children: [iconNode ?? (_jsx("span", { className: "testimonial-card__mark", "aria-hidden": "true", children: "\u201C" })), _jsx("p", { className: "testimonial-card__quote", "data-editable-target": `items[${idx}].quote`, "data-editable-target-label": `items[${idx}].quote`, "data-editable-label": `items[${idx}].quote`, children: String(row.quote ?? "") }), _jsxs("footer", { className: "testimonial-card__footer", children: [imageUrl.length > 0 && (_jsx(BlockImage, { className: "testimonial-card__avatar", src: imageUrl, alt: imageAlt.length > 0 ? imageAlt : String(row.author ?? ""), width: 48, height: 48, loading: "lazy", "data-editable-target": `items[${idx}].imageUrl`, "data-editable-target-label": `items[${idx}].imageUrl`, "data-editable-label": `items[${idx}].imageUrl` })), _jsxs("div", { className: "testimonial-card__attribution", children: [_jsxs("span", { className: "testimonial-card__author", "data-editable-target": `items[${idx}].author`, "data-editable-target-label": `items[${idx}].author`, "data-editable-label": `items[${idx}].author`, children: ["\u2014 ", String(row.author ?? "")] }), role.length > 0 && (_jsx("span", { className: "testimonial-card__role", "data-editable-target": `items[${idx}].role`, "data-editable-target-label": `items[${idx}].role`, "data-editable-label": `items[${idx}].role`, children: role }))] })] })] }, idx));
14
+ }) })] }) }));
15
+ }
@@ -0,0 +1,92 @@
1
+ .testimonials-grid {
2
+ display: grid;
3
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
4
+ gap: 16px;
5
+ margin-top: 16px;
6
+ }
7
+
8
+ .testimonial-card {
9
+ margin: 0;
10
+ padding: 20px 24px 20px;
11
+ border-radius: var(--radius-card);
12
+ background: var(--surface);
13
+ border-left: 3px solid var(--brand);
14
+ position: relative;
15
+ }
16
+
17
+ .testimonial-card__mark {
18
+ font-size: 3rem;
19
+ line-height: 1;
20
+ color: var(--brand);
21
+ opacity: 0.25;
22
+ display: block;
23
+ margin-bottom: 6px;
24
+ font-family: Georgia, serif;
25
+ }
26
+
27
+ .testimonial-card__icon {
28
+ display: block;
29
+ font-size: 1.75rem;
30
+ line-height: 1;
31
+ margin-bottom: 10px;
32
+ width: 36px;
33
+ height: 36px;
34
+ object-fit: contain;
35
+ }
36
+
37
+ img.testimonial-card__icon {
38
+ border-radius: 8px;
39
+ }
40
+
41
+ .testimonial-card__quote {
42
+ margin: 0 0 14px;
43
+ font-style: italic;
44
+ color: var(--body);
45
+ font-size: 1rem;
46
+ line-height: 1.62;
47
+ }
48
+
49
+ .testimonial-card__footer {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 12px;
53
+ }
54
+
55
+ .testimonial-card__avatar {
56
+ width: 48px;
57
+ height: 48px;
58
+ border-radius: 50%;
59
+ object-fit: cover;
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ .testimonial-card__attribution {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 2px;
67
+ }
68
+
69
+ .testimonial-card__author {
70
+ font-size: 0.9375rem;
71
+ font-weight: 700;
72
+ letter-spacing: 0.005em;
73
+ color: var(--caption);
74
+ }
75
+
76
+ .testimonial-card__role {
77
+ font-size: 0.8125rem;
78
+ color: var(--body-secondary);
79
+ font-weight: 400;
80
+ }
81
+
82
+ /* Dark */
83
+ .dark .testimonial-card__mark {
84
+ color: #22d3ee;
85
+ opacity: 0.35;
86
+ }
87
+
88
+ @media (max-width: 900px) {
89
+ .testimonials-grid {
90
+ grid-template-columns: 1fr;
91
+ }
92
+ }
@@ -0,0 +1 @@
1
+ export declare function TwoColumn(props: Record<string, unknown>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolveHeadingTag } from "@avocadostudio-ai/shared";
3
+ import { PrimaryButton, renderRichTextContent, BlockImage } from "../_shared.js";
4
+ function TwoColumnChild({ item, headingTag, pathPrefix }) {
5
+ const childType = String(item.type ?? "");
6
+ const HeadingTag = headingTag;
7
+ if (childType === "heading") {
8
+ return (_jsx(HeadingTag, { "data-editable-target": "heading", "data-editable-target-label": "heading", "data-editable-label": "heading", children: String(item.text ?? "") }));
9
+ }
10
+ if (childType === "paragraph") {
11
+ const renderedBody = renderRichTextContent(String(item.text ?? ""));
12
+ return (_jsx("div", { className: "two-column__body", "data-editable-target": "body", "data-editable-target-label": "body", "data-editable-label": "body", children: renderedBody }));
13
+ }
14
+ if (childType === "cta") {
15
+ const label = String(item.label ?? "");
16
+ if (!label)
17
+ return null;
18
+ return (_jsx("div", { className: "two-column__cta", children: _jsx(PrimaryButton, { href: String(item.href ?? "#"), "data-editable-target": "ctaText", "data-editable-target-label": "ctaText", "data-editable-label": "ctaText", children: label }) }));
19
+ }
20
+ if (childType === "video") {
21
+ const src = String(item.src ?? "");
22
+ const poster = item.poster ? String(item.poster) : undefined;
23
+ if (!src)
24
+ return null;
25
+ return (_jsx("div", { className: "two-column__media", children: _jsx("video", { className: "two-column__video", controls: true, playsInline: true, preload: "metadata", poster: poster, children: _jsx("source", { src: src, type: "video/mp4" }) }) }));
26
+ }
27
+ if (childType === "image") {
28
+ return (_jsx("div", { className: "two-column__media", "data-editable-target": `${pathPrefix}.src`, "data-editable-target-label": "Image", children: _jsx(BlockImage, { className: "layout-grid__img", src: String(item.src ?? ""), alt: String(item.alt ?? ""), width: 768, height: 1024, sizes: "(max-width: 768px) 100vw, 50vw", "data-editable-label": "Image" }) }));
29
+ }
30
+ return null;
31
+ }
32
+ export function TwoColumn(props) {
33
+ const leftItems = Array.isArray(props.left) ? props.left : [];
34
+ const rightItems = Array.isArray(props.right) ? props.right : [];
35
+ const variant = String(props.variant ?? "default");
36
+ const HeadingTag = resolveHeadingTag("TwoColumn", props);
37
+ const allItems = [...leftItems, ...rightItems];
38
+ const hasVideo = allItems.some((item) => String(item.type ?? "") === "video");
39
+ const accentClass = variant === "accent" || hasVideo ? " two-column--accent" : "";
40
+ return (_jsx("section", { className: `two-column${accentClass}`, children: _jsxs("div", { className: "section__inner two-column__inner layout-grid layout-grid--balanced", children: [_jsx("div", { className: "two-column__text layout-grid__col", children: leftItems.map((item, i) => (_jsx(TwoColumnChild, { item: item, headingTag: HeadingTag, pathPrefix: `left[${i}]` }, `l-${i}`))) }), _jsx("div", { className: "two-column__text layout-grid__col", children: rightItems.map((item, i) => (_jsx(TwoColumnChild, { item: item, headingTag: HeadingTag, pathPrefix: `right[${i}]` }, `r-${i}`))) })] }) }));
41
+ }
@@ -0,0 +1,83 @@
1
+ .two-column {
2
+ padding: 40px 20px;
3
+ }
4
+
5
+ .two-column__inner {
6
+ gap: 32px;
7
+ }
8
+
9
+ .two-column__text h2 {
10
+ margin: 0 0 12px;
11
+ font-size: clamp(1.75rem, 2.3vw, 2.15rem);
12
+ line-height: 1.15;
13
+ letter-spacing: -0.015em;
14
+ font-weight: 800;
15
+ color: var(--heading);
16
+ }
17
+
18
+ .two-column__body {
19
+ font-size: 1rem;
20
+ line-height: 1.65;
21
+ color: var(--body-secondary);
22
+ margin-bottom: 16px;
23
+ }
24
+
25
+ .two-column__body p {
26
+ font-size: 1rem;
27
+ line-height: 1.75;
28
+ color: var(--body-secondary);
29
+ margin-bottom: 1em;
30
+ }
31
+
32
+ .two-column__body p:last-child {
33
+ margin-bottom: 0;
34
+ }
35
+
36
+ .two-column__body ul,
37
+ .two-column__body ol {
38
+ margin: 0 0 1em;
39
+ padding-left: 1.4em;
40
+ color: var(--body-secondary);
41
+ line-height: 1.75;
42
+ }
43
+
44
+ .two-column__body ul {
45
+ list-style: disc;
46
+ }
47
+
48
+ .two-column__body ol {
49
+ list-style: decimal;
50
+ }
51
+
52
+ .two-column__body li {
53
+ display: list-item;
54
+ }
55
+
56
+ .two-column__body li + li {
57
+ margin-top: 0.3em;
58
+ }
59
+
60
+ .two-column__body h3 {
61
+ margin-top: 1.25em;
62
+ margin-bottom: 0.45em;
63
+ font-size: 1.25rem;
64
+ line-height: 1.3;
65
+ letter-spacing: -0.01em;
66
+ font-weight: 700;
67
+ color: var(--heading);
68
+ }
69
+
70
+ .two-column__cta {
71
+ margin-top: 8px;
72
+ }
73
+
74
+ .two-column__media img {
75
+ border-radius: var(--radius-btn);
76
+ background: var(--placeholder-img);
77
+ }
78
+
79
+ @media (max-width: 900px) {
80
+ .two-column--image-left .two-column__media {
81
+ order: 0;
82
+ }
83
+ }
@@ -0,0 +1,2 @@
1
+ import { type JSX } from "react";
2
+ export declare function Video(props: Record<string, unknown>): JSX.Element;
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { renderInline } from "../_shared.js";
5
+ function extractYouTubeId(url) {
6
+ const match = url.match(/(?:youtube\.com\/(?:watch\?.*v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{11})/);
7
+ return match?.[1] ?? null;
8
+ }
9
+ function extractVimeoId(url) {
10
+ const match = url.match(/vimeo\.com\/(\d+)/);
11
+ return match?.[1] ?? null;
12
+ }
13
+ function detectSourceType(src) {
14
+ if (extractYouTubeId(src))
15
+ return "youtube";
16
+ if (extractVimeoId(src))
17
+ return "vimeo";
18
+ return "direct";
19
+ }
20
+ function buildEmbedUrl(type, id, autoplay, loop) {
21
+ if (type === "youtube") {
22
+ const params = new URLSearchParams();
23
+ params.set("autoplay", "1");
24
+ if (loop)
25
+ params.set("loop", "1");
26
+ return `https://www.youtube.com/embed/${id}?${params.toString()}`;
27
+ }
28
+ const params = new URLSearchParams();
29
+ params.set("autoplay", "1");
30
+ if (loop)
31
+ params.set("loop", "1");
32
+ return `https://player.vimeo.com/video/${id}?${params.toString()}`;
33
+ }
34
+ function EmbedFacade({ type, videoId, title, autoplay, loop }) {
35
+ const [activated, setActivated] = useState(autoplay);
36
+ if (activated) {
37
+ return (_jsx("iframe", { src: buildEmbedUrl(type, videoId, true, loop), title: title || "Video", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true }));
38
+ }
39
+ const thumbUrl = type === "youtube"
40
+ ? `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`
41
+ : undefined;
42
+ return (_jsxs("button", { type: "button", className: "video-block__facade", onClick: () => setActivated(true), "aria-label": `Play ${title || "video"}`, children: [thumbUrl && _jsx("img", { src: thumbUrl, alt: "", className: "video-block__facade-thumb", loading: "lazy" }), _jsxs("svg", { className: "video-block__facade-play", viewBox: "0 0 68 48", "aria-hidden": "true", children: [_jsx("path", { d: "M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z", fill: "currentColor" }), _jsx("path", { d: "M45 24L27 14v20", fill: "#fff" })] })] }));
43
+ }
44
+ export function Video(props) {
45
+ const src = String(props.src ?? "");
46
+ const title = String(props.title ?? "");
47
+ const posterUrl = String(props.posterUrl ?? "");
48
+ const autoplay = String(props.autoplay) === "true";
49
+ const loop = String(props.loop) === "true";
50
+ const sourceType = detectSourceType(src);
51
+ return (_jsx("section", { className: "video-block", children: _jsxs("div", { className: "section__inner", children: [_jsxs("div", { className: "video-block__frame", children: [sourceType === "youtube" && (_jsx(EmbedFacade, { type: "youtube", videoId: extractYouTubeId(src), title: title, autoplay: autoplay, loop: loop })), sourceType === "vimeo" && (_jsx(EmbedFacade, { type: "vimeo", videoId: extractVimeoId(src), title: title, autoplay: autoplay, loop: loop })), sourceType === "direct" && (_jsx("video", { src: src, poster: posterUrl || undefined, controls: true, autoPlay: autoplay, loop: loop, playsInline: true, preload: "metadata" }))] }), title.length > 0 && (_jsx("p", { className: "video-block__caption", "data-editable-target": "title", "data-editable-target-label": "title", "data-editable-label": "title", children: renderInline(title) }))] }) }));
52
+ }
@@ -0,0 +1,84 @@
1
+ .video-block {
2
+ padding: 40px 20px;
3
+ background: var(--section-bg);
4
+ }
5
+
6
+ .video-block .section__inner {
7
+ max-width: 960px;
8
+ display: grid;
9
+ row-gap: 12px;
10
+ justify-items: center;
11
+ }
12
+
13
+ .video-block__frame {
14
+ width: 100%;
15
+ aspect-ratio: 16 / 9;
16
+ position: relative;
17
+ overflow: hidden;
18
+ border-radius: var(--radius-card, 12px);
19
+ background: #000;
20
+ }
21
+
22
+ .video-block__frame iframe,
23
+ .video-block__frame video {
24
+ position: absolute;
25
+ inset: 0;
26
+ width: 100%;
27
+ height: 100%;
28
+ border: 0;
29
+ display: block;
30
+ }
31
+
32
+ .video-block__facade {
33
+ position: absolute;
34
+ inset: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ border: 0;
38
+ padding: 0;
39
+ cursor: pointer;
40
+ background: #000;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ }
45
+
46
+ .video-block__facade-thumb {
47
+ position: absolute;
48
+ inset: 0;
49
+ width: 100%;
50
+ height: 100%;
51
+ object-fit: cover;
52
+ }
53
+
54
+ .video-block__facade-play {
55
+ position: relative;
56
+ z-index: 1;
57
+ width: 68px;
58
+ height: 48px;
59
+ color: var(--brand, #e00);
60
+ opacity: 0.85;
61
+ transition: opacity 0.15s;
62
+ }
63
+
64
+ .video-block__facade:hover .video-block__facade-play {
65
+ opacity: 1;
66
+ }
67
+
68
+ .video-block__caption {
69
+ margin: 0;
70
+ font-size: 0.95rem;
71
+ color: var(--text-200);
72
+ text-align: center;
73
+ text-wrap: pretty;
74
+ }
75
+
76
+ @media (max-width: 600px) {
77
+ .video-block {
78
+ padding: 24px 12px;
79
+ }
80
+
81
+ .video-block__frame {
82
+ border-radius: var(--radius-card, 8px);
83
+ }
84
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Client component that hydrates interactive blocks (carousel, tabs, etc.)
3
+ * after the page renders. Drop this anywhere in the component tree.
4
+ */
5
+ export declare function BlocksHydrator(): null;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { useEffect } from "react";
3
+ import { initCarousels } from "./blocks/carousel/init.js";
4
+ import { initTabs } from "./blocks/tabs/init.js";
5
+ /**
6
+ * Client component that hydrates interactive blocks (carousel, tabs, etc.)
7
+ * after the page renders. Drop this anywhere in the component tree.
8
+ */
9
+ export function BlocksHydrator() {
10
+ useEffect(() => {
11
+ initCarousels();
12
+ initTabs();
13
+ });
14
+ return null;
15
+ }
@@ -0,0 +1 @@
1
+ export declare function BlockCatalogue(): import("react/jsx-runtime").JSX.Element;