@sumaq/site-kit 0.2.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 (60) hide show
  1. package/README.md +93 -0
  2. package/package.json +57 -0
  3. package/src/astro-modules.d.ts +11 -0
  4. package/src/blocks/About.astro +94 -0
  5. package/src/blocks/Audience.astro +66 -0
  6. package/src/blocks/BlockRenderer.astro +105 -0
  7. package/src/blocks/Contact.astro +58 -0
  8. package/src/blocks/ContactDetails.astro +91 -0
  9. package/src/blocks/CtaBand.astro +53 -0
  10. package/src/blocks/EmergencyGrid.astro +47 -0
  11. package/src/blocks/EmergencyHelplines.astro +109 -0
  12. package/src/blocks/EmergencyPrimary.astro +51 -0
  13. package/src/blocks/Faq.astro +56 -0
  14. package/src/blocks/FeatureList.astro +73 -0
  15. package/src/blocks/Gallery.astro +95 -0
  16. package/src/blocks/Generic.astro +30 -0
  17. package/src/blocks/Hero.astro +82 -0
  18. package/src/blocks/IntroCta.astro +41 -0
  19. package/src/blocks/LegalDocument.astro +83 -0
  20. package/src/blocks/LocationsOverview.astro +161 -0
  21. package/src/blocks/NotFound.astro +33 -0
  22. package/src/blocks/Notice.astro +49 -0
  23. package/src/blocks/PageBanner.astro +57 -0
  24. package/src/blocks/Pricing.astro +97 -0
  25. package/src/blocks/ProfileDetail.astro +202 -0
  26. package/src/blocks/Projects.astro +91 -0
  27. package/src/blocks/Quote.astro +53 -0
  28. package/src/blocks/Services.astro +46 -0
  29. package/src/blocks/Skills.astro +65 -0
  30. package/src/blocks/Stats.astro +54 -0
  31. package/src/blocks/Steps.astro +76 -0
  32. package/src/blocks/TeamDirectory.astro +164 -0
  33. package/src/blocks/TeamTeaser.astro +84 -0
  34. package/src/blocks/Testimonials.astro +75 -0
  35. package/src/blocks/Timeline.astro +65 -0
  36. package/src/components/Actions.astro +67 -0
  37. package/src/components/Card.astro +104 -0
  38. package/src/components/Logo.astro +57 -0
  39. package/src/components/Media.astro +83 -0
  40. package/src/components/Prose.astro +29 -0
  41. package/src/components/ScrollToTop.astro +49 -0
  42. package/src/components/Section.astro +75 -0
  43. package/src/components/SectionHeader.astro +58 -0
  44. package/src/components/SectionHeading.astro +76 -0
  45. package/src/components/Seo.astro +55 -0
  46. package/src/components/SiteFooter.astro +90 -0
  47. package/src/components/SiteHeader.astro +97 -0
  48. package/src/config.ts +59 -0
  49. package/src/index.ts +75 -0
  50. package/src/layouts/Base.astro +57 -0
  51. package/src/lib/collections.ts +101 -0
  52. package/src/lib/content.ts +51 -0
  53. package/src/lib/format.ts +17 -0
  54. package/src/lib/markdown.ts +5 -0
  55. package/src/profiles/clinic.yaml +35 -0
  56. package/src/profiles/portfolio.yaml +27 -0
  57. package/src/profiles/therapist.yaml +31 -0
  58. package/src/scripts/enhancements.js +459 -0
  59. package/src/tokens/class-inventory.json +284 -0
  60. package/src/tokens/index.css +83 -0
@@ -0,0 +1,109 @@
1
+ ---
2
+ import { cmsKey, hasAny, hasText, list } from "../lib/content";
3
+
4
+ const {
5
+ sectionTitle,
6
+ items = [],
7
+ disclaimer,
8
+ /**
9
+ * Lead-in emphasised at the start of the disclaimer. The German default is
10
+ * the pre-0.2 behaviour kept so the live site keeps its emphasis; move it to
11
+ * content and drop the fallback.
12
+ */
13
+ disclaimerLabel = "Wichtiger Hinweis:",
14
+ backLabel,
15
+ backHref,
16
+ section = "emergencyHelplines",
17
+ } = Astro.props;
18
+
19
+ const cms = section;
20
+ const entries = list<any>(items).filter((item) => hasText(item.title) || hasText(item.actionLabel));
21
+
22
+ /** The label may already open the stored text; do not print it twice. */
23
+ const disclaimerBody = hasText(disclaimer)
24
+ ? String(disclaimer).replace(new RegExp(`^${disclaimerLabel}\\s*`, "i"), "")
25
+ : "";
26
+ const showBack = hasText(backHref) && hasText(backLabel);
27
+ ---
28
+
29
+ {
30
+ hasText(sectionTitle) && (
31
+ <h2
32
+ class="sq-section__title sq-center sq-section__title--compact sq-section__title--spaced"
33
+ data-cms={cmsKey(cms, "sectionTitle")}
34
+ >
35
+ {sectionTitle}
36
+ </h2>
37
+ )
38
+ }
39
+
40
+ {
41
+ entries.length > 0 && (
42
+ <ul class="sq-contact__list--narrow" data-cms={cms}>
43
+ {entries.map((item: any, i: number) => (
44
+ <li>
45
+ {hasAny(item.title, item.desc) && (
46
+ <div class="sq-contact__item-info">
47
+ {hasText(item.title) && (
48
+ <span class="sq-contact__item-title" data-cms={cmsKey(cms, "items", i, "title")}>
49
+ {item.title}
50
+ </span>
51
+ )}
52
+ {hasText(item.desc) && (
53
+ <span class="sq-contact__item-desc" data-cms={cmsKey(cms, "items", i, "desc")}>
54
+ {item.desc}
55
+ </span>
56
+ )}
57
+ </div>
58
+ )}
59
+ {hasText(item.actionLabel) &&
60
+ hasText(item.actionHref) &&
61
+ (item.isExternal ? (
62
+ <span class="sq-contact__item-value">
63
+ <a
64
+ href={item.actionHref}
65
+ target="_blank"
66
+ rel="noopener"
67
+ data-cms={cmsKey(cms, "items", i, "actionLabel")}
68
+ >
69
+ {item.actionLabel}
70
+ </a>
71
+ </span>
72
+ ) : (
73
+ <a
74
+ href={item.actionHref}
75
+ class="sq-contact__item-action"
76
+ data-cms={cmsKey(cms, "items", i, "actionLabel")}
77
+ >
78
+ {item.actionLabel}
79
+ </a>
80
+ ))}
81
+ </li>
82
+ ))}
83
+ </ul>
84
+ )
85
+ }
86
+
87
+ {
88
+ (hasText(disclaimer) || showBack) && (
89
+ <div class="sq-center sq-stack-end">
90
+ {hasText(disclaimer) && (
91
+ <p
92
+ class="sq-section__lede sq-section__lede--sm sq-center"
93
+ data-cms={cmsKey(cms, "disclaimer")}
94
+ >
95
+ {hasText(disclaimerLabel) && <em>{disclaimerLabel}</em>} {disclaimerBody}
96
+ </p>
97
+ )}
98
+ {showBack && (
99
+ <a
100
+ class="sq-btn sq-btn--ghost sq-btn--spaced"
101
+ href={backHref}
102
+ data-cms={cmsKey(cms, "backLabel")}
103
+ >
104
+ {backLabel}
105
+ </a>
106
+ )}
107
+ </div>
108
+ )
109
+ }
@@ -0,0 +1,51 @@
1
+ ---
2
+ import { cmsKey, hasAny, hasText } from "../lib/content";
3
+
4
+ const {
5
+ sectionTitle,
6
+ sectionSubtitle,
7
+ phoneLabel,
8
+ phone,
9
+ phoneHref,
10
+ hours,
11
+ section = "emergencyPrimary",
12
+ } = Astro.props;
13
+ const cms = section;
14
+ const hasPhone = hasText(phone) && hasText(phoneHref);
15
+ ---
16
+
17
+ {
18
+ hasAny(sectionTitle, sectionSubtitle, phone, hours) && (
19
+ <div class="sq-emergency__card" data-cms={cms}>
20
+ {hasText(sectionTitle) && (
21
+ <h2 class="sq-emergency__title" data-cms={cmsKey(cms, "sectionTitle")}>
22
+ {sectionTitle}
23
+ </h2>
24
+ )}
25
+ {hasText(sectionSubtitle) && (
26
+ <p class="sq-section__lede sq-center" data-cms={cmsKey(cms, "sectionSubtitle")}>
27
+ {sectionSubtitle}
28
+ </p>
29
+ )}
30
+ {(hasPhone || hasText(phoneLabel)) && (
31
+ <div class="sq-emergency__phone">
32
+ {hasText(phoneLabel) && (
33
+ <span class="sq-emergency__phone-label" data-cms={cmsKey(cms, "phoneLabel")}>
34
+ {phoneLabel}
35
+ </span>
36
+ )}
37
+ {hasPhone && (
38
+ <a href={phoneHref} class="sq-emergency__phone-number" data-cms={cmsKey(cms, "phone")}>
39
+ {phone}
40
+ </a>
41
+ )}
42
+ </div>
43
+ )}
44
+ {hasText(hours) && (
45
+ <p class="sq-emergency__hours" data-cms={cmsKey(cms, "hours")}>
46
+ {hours}
47
+ </p>
48
+ )}
49
+ </div>
50
+ )
51
+ }
@@ -0,0 +1,56 @@
1
+ ---
2
+ /**
3
+ * Questions and answers as native disclosure widgets.
4
+ *
5
+ * Not extracted from an existing site — none of the five had one — but a
6
+ * standard slot, and `<details>` keeps it keyboard-accessible with no JS.
7
+ */
8
+ import Section from "../components/Section.astro";
9
+ import SectionHeader from "../components/SectionHeader.astro";
10
+ import Prose from "../components/Prose.astro";
11
+ import { cmsKey, hasText, list } from "../lib/content";
12
+
13
+ const {
14
+ sectionLabel,
15
+ sectionTitle,
16
+ sectionTitleAccent,
17
+ sectionSubtitle,
18
+ items = [],
19
+ /** Only one answer open at a time (native `<details name>` grouping). */
20
+ exclusive = false,
21
+ section = "faq",
22
+ variant = "light",
23
+ id = "faq",
24
+ } = Astro.props;
25
+
26
+ const cms = section;
27
+ const entries = list<any>(items).filter((item) => hasText(item.question) && hasText(item.answer));
28
+ ---
29
+
30
+ <Section name="faq" variant={variant} id={id} container="narrow" cms={cms}>
31
+ <SectionHeader
32
+ label={sectionLabel}
33
+ title={sectionTitle}
34
+ titleAccent={sectionTitleAccent}
35
+ lede={sectionSubtitle}
36
+ cms={cms}
37
+ />
38
+ {
39
+ entries.length > 0 && (
40
+ <div class="sq-faq__body">
41
+ {entries.map((item: any, i: number) => (
42
+ <details class="sq-faq__item" name={exclusive ? `${cms}-item` : undefined}>
43
+ <summary class="sq-faq__question" data-cms={cmsKey(cms, "items", i, "question")}>
44
+ {item.question}
45
+ </summary>
46
+ <Prose
47
+ body={item.answer}
48
+ class="sq-faq__answer"
49
+ cms={cmsKey(cms, "items", i, "answer")}
50
+ />
51
+ </details>
52
+ ))}
53
+ </div>
54
+ )
55
+ }
56
+ </Section>
@@ -0,0 +1,73 @@
1
+ ---
2
+ /**
3
+ * A checklist of short statements — the shape that appears as `ItemList`
4
+ * (www-utehofer-at), the `scenarios` list (www-bettinageidl-at), `focus-list`
5
+ * (www-juliaschaffer-at) and `sq-feature-list` (www-therapieraum-graz-at).
6
+ *
7
+ * Entries may be plain strings or `{ title, text, icon }` objects.
8
+ */
9
+ import Section from "../components/Section.astro";
10
+ import SectionHeader from "../components/SectionHeader.astro";
11
+ import Media from "../components/Media.astro";
12
+ import { cmsKey, hasImage, hasText, list } from "../lib/content";
13
+
14
+ const {
15
+ sectionLabel,
16
+ sectionTitle,
17
+ sectionTitleAccent,
18
+ sectionSubtitle,
19
+ items = [],
20
+ /** `centered` adds `sq-feature-list--centered`. */
21
+ align = "start",
22
+ columns,
23
+ section = "featureList",
24
+ variant = "light",
25
+ id,
26
+ } = Astro.props;
27
+
28
+ const cms = section;
29
+ const entries = list<any>(items)
30
+ .map((item) => (typeof item === "string" ? { text: item } : item))
31
+ .filter((item) => hasText(item.text) || hasText(item.title));
32
+ ---
33
+
34
+ <Section name="features" variant={variant} id={id} cms={cms}>
35
+ <SectionHeader
36
+ label={sectionLabel}
37
+ title={sectionTitle}
38
+ titleAccent={sectionTitleAccent}
39
+ lede={sectionSubtitle}
40
+ cms={cms}
41
+ />
42
+ {
43
+ entries.length > 0 && (
44
+ <ul
45
+ class:list={[
46
+ "sq-feature-list",
47
+ align === "center" && "sq-feature-list--centered",
48
+ columns && `sq-feature-list--${columns}`,
49
+ ]}
50
+ >
51
+ {entries.map((item: any, i: number) => (
52
+ <li>
53
+ {hasImage(item.icon) && (
54
+ <span class="sq-feature-list__icon" aria-hidden="true">
55
+ <Media src={item.icon} alt="" cms={cmsKey(cms, "items", i, "icon")} />
56
+ </span>
57
+ )}
58
+ {hasText(item.title) && (
59
+ <span class="sq-feature-list__title" data-cms={cmsKey(cms, "items", i, "title")}>
60
+ {item.title}
61
+ </span>
62
+ )}
63
+ {hasText(item.text) && (
64
+ <span class="sq-feature-list__text" data-cms={cmsKey(cms, "items", i, "text")}>
65
+ {item.text}
66
+ </span>
67
+ )}
68
+ </li>
69
+ ))}
70
+ </ul>
71
+ )
72
+ }
73
+ </Section>
@@ -0,0 +1,95 @@
1
+ ---
2
+ /**
3
+ * Image gallery. Extracted from `Mosaic.astro` / `MosaicGallery.astro`
4
+ * (www-utehofer-at) and `GallerySection.astro` / `MosaicGrid.astro`
5
+ * (www-juliaschaffer-at), which between them carry ~650 lines of layout CSS
6
+ * inside the component. Here the markup is a flat list; the mosaic is a CSS
7
+ * decision the site makes on `sq-gallery__body` and `:nth-child()`.
8
+ *
9
+ * `lightbox` opts into the viewer wired in `scripts/enhancements.js`.
10
+ */
11
+ import Section from "../components/Section.astro";
12
+ import SectionHeader from "../components/SectionHeader.astro";
13
+ import Media from "../components/Media.astro";
14
+ import { cmsKey, hasText, list } from "../lib/content";
15
+
16
+ const {
17
+ sectionLabel,
18
+ sectionTitle,
19
+ sectionTitleAccent,
20
+ sectionSubtitle,
21
+ items = [],
22
+ lightbox = false,
23
+ closeLabel = "Close",
24
+ prevLabel = "Previous",
25
+ nextLabel = "Next",
26
+ section = "gallery",
27
+ variant = "light",
28
+ id = "gallery",
29
+ } = Astro.props;
30
+
31
+ const cms = section;
32
+ const images = list<any>(items)
33
+ .map((item) => (typeof item === "string" ? { src: item } : item))
34
+ .filter((item) => hasText(item.src));
35
+ ---
36
+
37
+ <Section name="gallery" variant={variant} id={id} cms={cms}>
38
+ <SectionHeader
39
+ label={sectionLabel}
40
+ title={sectionTitle}
41
+ titleAccent={sectionTitleAccent}
42
+ lede={sectionSubtitle}
43
+ cms={cms}
44
+ />
45
+ {
46
+ images.length > 0 && (
47
+ <ul class="sq-gallery__body" role="list" data-gallery={lightbox ? "" : undefined}>
48
+ {images.map((image: any, i: number) => (
49
+ <li class="sq-gallery__item">
50
+ {lightbox ? (
51
+ <button
52
+ type="button"
53
+ class="sq-gallery__trigger"
54
+ data-gallery-index={i}
55
+ data-src={image.src}
56
+ data-alt={image.alt || ""}
57
+ aria-label={image.alt || undefined}
58
+ >
59
+ <Media
60
+ src={image.src}
61
+ alt={image.alt}
62
+ cms={cmsKey(cms, "items", i, "src")}
63
+ />
64
+ </button>
65
+ ) : (
66
+ <Media src={image.src} alt={image.alt} cms={cmsKey(cms, "items", i, "src")} />
67
+ )}
68
+ {hasText(image.caption) && (
69
+ <p class="sq-gallery__caption" data-cms={cmsKey(cms, "items", i, "caption")}>
70
+ {image.caption}
71
+ </p>
72
+ )}
73
+ </li>
74
+ ))}
75
+ </ul>
76
+ )
77
+ }
78
+ </Section>
79
+
80
+ {
81
+ lightbox && images.length > 0 && (
82
+ <div class="sq-lightbox" data-lightbox role="dialog" aria-modal="true" hidden>
83
+ <button type="button" class="sq-lightbox__close" data-lightbox-close aria-label={closeLabel}>
84
+ <span aria-hidden="true">×</span>
85
+ </button>
86
+ <button type="button" class="sq-lightbox__prev" data-lightbox-prev aria-label={prevLabel}>
87
+ <span aria-hidden="true">‹</span>
88
+ </button>
89
+ <img class="sq-lightbox__image" data-lightbox-image alt="" />
90
+ <button type="button" class="sq-lightbox__next" data-lightbox-next aria-label={nextLabel}>
91
+ <span aria-hidden="true">›</span>
92
+ </button>
93
+ </div>
94
+ )
95
+ }
@@ -0,0 +1,30 @@
1
+ ---
2
+ import Section from "../components/Section.astro";
3
+ import SectionHeading from "../components/SectionHeading.astro";
4
+ import Prose from "../components/Prose.astro";
5
+ import { cmsKey } from "../lib/content";
6
+
7
+ const {
8
+ sectionTitle,
9
+ sectionTitleAccent,
10
+ sectionLabel,
11
+ sectionSubtitle,
12
+ body,
13
+ type,
14
+ section,
15
+ variant = "light",
16
+ id,
17
+ } = Astro.props;
18
+ const cms = section ?? type ?? "generic";
19
+ ---
20
+
21
+ <Section name="generic" variant={variant} id={id} container="narrow" cms={cms}>
22
+ <SectionHeading
23
+ label={sectionLabel}
24
+ title={sectionTitle}
25
+ titleAccent={sectionTitleAccent}
26
+ lede={sectionSubtitle}
27
+ cms={cms}
28
+ />
29
+ <Prose body={body} cms={cmsKey(cms, "body")} />
30
+ </Section>
@@ -0,0 +1,82 @@
1
+ ---
2
+ import Media from "../components/Media.astro";
3
+ import Actions from "../components/Actions.astro";
4
+ import { cmsKey, hasImage, hasText, list } from "../lib/content";
5
+
6
+ const {
7
+ eyebrow,
8
+ title,
9
+ titleAccent,
10
+ image,
11
+ sectionSubtitle,
12
+ ctas = [],
13
+ stats = [],
14
+ section = "hero",
15
+ } = Astro.props;
16
+
17
+ const cms = section;
18
+ const statItems = list<any>(stats).filter((stat) => hasText(stat.label) || hasText(stat.value));
19
+ const hasTitle = hasText(title) || hasText(titleAccent);
20
+ ---
21
+
22
+ <section
23
+ class="sq-hero"
24
+ aria-labelledby={hasTitle ? "hero-title" : undefined}
25
+ data-cms={cms}
26
+ >
27
+ {
28
+ hasImage(image) && (
29
+ <div class="sq-hero__media" aria-hidden="true">
30
+ <Media
31
+ src={image}
32
+ alt=""
33
+ width={960}
34
+ height={960}
35
+ loading="eager"
36
+ fetchpriority="high"
37
+ cms={cmsKey(cms, "image", "src")}
38
+ />
39
+ <div class="sq-hero__overlay" />
40
+ </div>
41
+ )
42
+ }
43
+ <div class="sq-container sq-hero__content">
44
+ {hasText(eyebrow) && <p class="sq-eyebrow" data-cms={cmsKey(cms, "eyebrow")}>{eyebrow}</p>}
45
+ {
46
+ hasTitle && (
47
+ <h1 id="hero-title" class="sq-hero__title" data-cms={cmsKey(cms, "title")}>
48
+ {title}
49
+ {hasText(titleAccent) && (
50
+ <span class="sq-accent" data-cms={cmsKey(cms, "titleAccent")}>
51
+ {titleAccent}
52
+ </span>
53
+ )}
54
+ </h1>
55
+ )
56
+ }
57
+ {
58
+ hasText(sectionSubtitle) && (
59
+ <p class="sq-hero__lede" data-cms={cmsKey(cms, "sectionSubtitle")}>
60
+ {sectionSubtitle}
61
+ </p>
62
+ )
63
+ }
64
+ <Actions ctas={ctas} class="sq-hero__actions" cms={cmsKey(cms, "ctas")} />
65
+ {
66
+ statItems.length > 0 && (
67
+ <dl class="sq-hero__stats">
68
+ {statItems.map((stat: any, i: number) => (
69
+ <div>
70
+ {hasText(stat.label) && (
71
+ <dt data-cms={cmsKey(cms, "stats", i, "label")}>{stat.label}</dt>
72
+ )}
73
+ {hasText(stat.value) && (
74
+ <dd data-cms={cmsKey(cms, "stats", i, "value")}>{stat.value}</dd>
75
+ )}
76
+ </div>
77
+ ))}
78
+ </dl>
79
+ )
80
+ }
81
+ </div>
82
+ </section>
@@ -0,0 +1,41 @@
1
+ ---
2
+ import Section from "../components/Section.astro";
3
+ import SectionHeading from "../components/SectionHeading.astro";
4
+ import Actions from "../components/Actions.astro";
5
+ import { cmsKey } from "../lib/content";
6
+
7
+ const {
8
+ sectionLabel,
9
+ sectionTitle,
10
+ sectionTitleAccent,
11
+ sectionSubtitle,
12
+ ctas = [],
13
+ section = "introCta",
14
+ variant = "muted",
15
+ id,
16
+ } = Astro.props;
17
+
18
+ const cms = section;
19
+ ---
20
+
21
+ <Section
22
+ name="introCta"
23
+ variant={variant}
24
+ id={id}
25
+ container="narrow"
26
+ containerClass="sq-center"
27
+ cms={cms}
28
+ >
29
+ <SectionHeading
30
+ label={sectionLabel}
31
+ title={sectionTitle}
32
+ titleAccent={sectionTitleAccent}
33
+ lede={sectionSubtitle}
34
+ cms={cms}
35
+ />
36
+ <Actions
37
+ ctas={ctas}
38
+ class="sq-hero__actions sq-hero__actions--center"
39
+ cms={cmsKey(cms, "ctas")}
40
+ />
41
+ </Section>
@@ -0,0 +1,83 @@
1
+ ---
2
+ /**
3
+ * Body of a legal page — Impressum, Datenschutz, AGB, privacy, terms.
4
+ *
5
+ * Every site solves this differently today: `MDLayout.astro` +
6
+ * `prose` (www-utehofer-at, www-bettinageidl-at), a hand-built
7
+ * `section.map(paragraphs)` (www-juliaschaffer-at), and `set:html={md(...)}`
8
+ * inlined in each page (www-therapieraum-graz-at). Accepts either a markdown
9
+ * `body` or a list of `{ heading, paragraphs }` sections.
10
+ */
11
+ import Section from "../components/Section.astro";
12
+ import Prose from "../components/Prose.astro";
13
+ import { cmsKey, hasItems, hasText, list } from "../lib/content";
14
+
15
+ const {
16
+ title,
17
+ lead,
18
+ body,
19
+ sections: parts = [],
20
+ updatedLabel,
21
+ updatedAt,
22
+ section = "legal",
23
+ variant = "light",
24
+ id = "legal-doc",
25
+ } = Astro.props;
26
+
27
+ const cms = section;
28
+ const chapters = list<any>(parts).filter(
29
+ (part) => hasText(part.heading) || hasItems(part.paragraphs),
30
+ );
31
+ ---
32
+
33
+ <Section
34
+ name="legal"
35
+ variant={variant}
36
+ container="narrow"
37
+ labelledby={hasText(title) ? `${id}-title` : undefined}
38
+ cms={cms}
39
+ >
40
+ {
41
+ (hasText(title) || hasText(lead) || hasText(updatedAt)) && (
42
+ <header class="sq-legal__head">
43
+ {hasText(title) && (
44
+ <h1 id={`${id}-title`} class="sq-section__title" data-cms={cmsKey(cms, "title")}>
45
+ {title}
46
+ </h1>
47
+ )}
48
+ {hasText(lead) && (
49
+ <p class="sq-section__lede" data-cms={cmsKey(cms, "lead")}>
50
+ {lead}
51
+ </p>
52
+ )}
53
+ {hasText(updatedAt) && (
54
+ <p class="sq-legal__meta" data-cms={cmsKey(cms, "updatedAt")}>
55
+ {hasText(updatedLabel) ? `${updatedLabel} ` : ""}
56
+ {updatedAt}
57
+ </p>
58
+ )}
59
+ </header>
60
+ )
61
+ }
62
+
63
+ <Prose as="article" body={body} id={id} class="sq-legal" cms={cmsKey(cms, "body")} />
64
+
65
+ {
66
+ chapters.length > 0 && (
67
+ <article class="sq-legal">
68
+ {chapters.map((part: any, i: number) => (
69
+ <section>
70
+ {hasText(part.heading) && (
71
+ <h2 data-cms={cmsKey(cms, "sections", i, "heading")}>{part.heading}</h2>
72
+ )}
73
+ {list<string>(part.paragraphs)
74
+ .filter(hasText)
75
+ .map((paragraph, pi) => (
76
+ <p data-cms={cmsKey(cms, "sections", i, "paragraphs", pi)}>{paragraph}</p>
77
+ ))}
78
+ </section>
79
+ ))}
80
+ </article>
81
+ )
82
+ }
83
+ </Section>