@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.
- package/LICENSE +201 -0
- package/dist/block-error-boundary.d.ts +18 -0
- package/dist/block-error-boundary.js +36 -0
- package/dist/blocks/_base.css +108 -0
- package/dist/blocks/_layout.css +47 -0
- package/dist/blocks/_shared.d.ts +24 -0
- package/dist/blocks/_shared.js +153 -0
- package/dist/blocks/_tokens.css +127 -0
- package/dist/blocks/banner/renderer.d.ts +2 -0
- package/dist/blocks/banner/renderer.js +21 -0
- package/dist/blocks/banner/styles.css +72 -0
- package/dist/blocks/block-image.d.ts +14 -0
- package/dist/blocks/block-image.js +10 -0
- package/dist/blocks/block-types.d.ts +2 -0
- package/dist/blocks/block-types.js +22 -0
- package/dist/blocks/card/renderer.d.ts +1 -0
- package/dist/blocks/card/renderer.js +11 -0
- package/dist/blocks/card/styles.css +94 -0
- package/dist/blocks/card-grid/renderer.d.ts +1 -0
- package/dist/blocks/card-grid/renderer.js +17 -0
- package/dist/blocks/card-grid/styles.css +38 -0
- package/dist/blocks/carousel/init.d.ts +8 -0
- package/dist/blocks/carousel/init.js +35 -0
- package/dist/blocks/carousel/renderer.d.ts +2 -0
- package/dist/blocks/carousel/renderer.js +19 -0
- package/dist/blocks/carousel/styles.css +136 -0
- package/dist/blocks/cta/renderer.d.ts +1 -0
- package/dist/blocks/cta/renderer.js +7 -0
- package/dist/blocks/cta/styles.css +111 -0
- package/dist/blocks/embed/renderer.d.ts +2 -0
- package/dist/blocks/embed/renderer.js +16 -0
- package/dist/blocks/embed/styles.css +58 -0
- package/dist/blocks/faq-accordion/renderer.d.ts +1 -0
- package/dist/blocks/faq-accordion/renderer.js +11 -0
- package/dist/blocks/faq-accordion/styles.css +68 -0
- package/dist/blocks/feature-grid/renderer.d.ts +1 -0
- package/dist/blocks/feature-grid/renderer.js +12 -0
- package/dist/blocks/feature-grid/styles.css +62 -0
- package/dist/blocks/footer/renderer.d.ts +1 -0
- package/dist/blocks/footer/renderer.js +19 -0
- package/dist/blocks/footer/styles.css +64 -0
- package/dist/blocks/gallery/renderer.d.ts +2 -0
- package/dist/blocks/gallery/renderer.js +21 -0
- package/dist/blocks/gallery/styles.css +59 -0
- package/dist/blocks/hero/renderer.d.ts +1 -0
- package/dist/blocks/hero/renderer.js +20 -0
- package/dist/blocks/hero/styles.css +137 -0
- package/dist/blocks/index.d.ts +5 -0
- package/dist/blocks/index.js +47 -0
- package/dist/blocks/quote/renderer.d.ts +2 -0
- package/dist/blocks/quote/renderer.js +9 -0
- package/dist/blocks/quote/styles.css +97 -0
- package/dist/blocks/rich-text/renderer.d.ts +1 -0
- package/dist/blocks/rich-text/renderer.js +15 -0
- package/dist/blocks/rich-text/styles.css +64 -0
- package/dist/blocks/site-header/renderer.d.ts +1 -0
- package/dist/blocks/site-header/renderer.js +64 -0
- package/dist/blocks/site-header/styles.css +345 -0
- package/dist/blocks/stats/renderer.d.ts +1 -0
- package/dist/blocks/stats/renderer.js +13 -0
- package/dist/blocks/stats/styles.css +64 -0
- package/dist/blocks/styles.css +23 -0
- package/dist/blocks/table/renderer.d.ts +2 -0
- package/dist/blocks/table/renderer.js +14 -0
- package/dist/blocks/table/styles.css +87 -0
- package/dist/blocks/tabs/init.d.ts +5 -0
- package/dist/blocks/tabs/init.js +38 -0
- package/dist/blocks/tabs/renderer.d.ts +2 -0
- package/dist/blocks/tabs/renderer.js +30 -0
- package/dist/blocks/tabs/styles.css +147 -0
- package/dist/blocks/testimonials/renderer.d.ts +1 -0
- package/dist/blocks/testimonials/renderer.js +15 -0
- package/dist/blocks/testimonials/styles.css +92 -0
- package/dist/blocks/two-column/renderer.d.ts +1 -0
- package/dist/blocks/two-column/renderer.js +41 -0
- package/dist/blocks/two-column/styles.css +83 -0
- package/dist/blocks/video/renderer.d.ts +2 -0
- package/dist/blocks/video/renderer.js +52 -0
- package/dist/blocks/video/styles.css +84 -0
- package/dist/blocks-hydrator.d.ts +5 -0
- package/dist/blocks-hydrator.js +15 -0
- package/dist/catalogue.d.ts +1 -0
- package/dist/catalogue.js +741 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/renderer.d.ts +12 -0
- package/dist/renderer.js +35 -0
- 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,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,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;
|