domma-cms 1.8.43 → 1.8.45

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 (56) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/admin/js/templates/docs/usage-blocks.html +29 -0
  3. package/docs/starter-blocks.md +203 -0
  4. package/package.json +1 -1
  5. package/server/blocks/catalog.json +98 -0
  6. package/server/blocks/domma-block-banner.css +65 -0
  7. package/server/blocks/domma-block-banner.html +22 -0
  8. package/server/blocks/domma-block-banner.meta.json +7 -0
  9. package/server/blocks/domma-block-contact.css +18 -0
  10. package/server/blocks/domma-block-contact.html +15 -0
  11. package/server/blocks/domma-block-contact.meta.json +7 -0
  12. package/server/blocks/domma-block-cta.css +8 -0
  13. package/server/blocks/domma-block-cta.html +17 -0
  14. package/server/blocks/domma-block-cta.meta.json +7 -0
  15. package/server/blocks/domma-block-faq.css +13 -0
  16. package/server/blocks/domma-block-faq.html +17 -0
  17. package/server/blocks/domma-block-faq.meta.json +7 -0
  18. package/server/blocks/domma-block-features.css +18 -0
  19. package/server/blocks/domma-block-features.html +19 -0
  20. package/server/blocks/domma-block-features.meta.json +7 -0
  21. package/server/blocks/domma-block-hero.css +15 -0
  22. package/server/blocks/domma-block-hero.html +13 -0
  23. package/server/blocks/domma-block-hero.meta.json +7 -0
  24. package/server/blocks/domma-block-image.css +8 -0
  25. package/server/blocks/domma-block-image.html +12 -0
  26. package/server/blocks/domma-block-image.meta.json +7 -0
  27. package/server/blocks/domma-block-logos.css +13 -0
  28. package/server/blocks/domma-block-logos.html +18 -0
  29. package/server/blocks/domma-block-logos.meta.json +7 -0
  30. package/server/blocks/domma-block-pricing-card.css +20 -0
  31. package/server/blocks/domma-block-pricing-card.html +15 -0
  32. package/server/blocks/domma-block-pricing-card.meta.json +7 -0
  33. package/server/blocks/domma-block-service-card.css +17 -0
  34. package/server/blocks/domma-block-service-card.html +15 -0
  35. package/server/blocks/domma-block-service-card.meta.json +7 -0
  36. package/server/blocks/domma-block-split.css +26 -0
  37. package/server/blocks/domma-block-split.html +22 -0
  38. package/server/blocks/domma-block-split.meta.json +7 -0
  39. package/server/blocks/domma-block-stats.css +20 -0
  40. package/server/blocks/domma-block-stats.html +16 -0
  41. package/server/blocks/domma-block-stats.meta.json +7 -0
  42. package/server/blocks/domma-block-steps.css +19 -0
  43. package/server/blocks/domma-block-steps.html +17 -0
  44. package/server/blocks/domma-block-steps.meta.json +7 -0
  45. package/server/blocks/domma-block-team-card.css +13 -0
  46. package/server/blocks/domma-block-team-card.html +14 -0
  47. package/server/blocks/domma-block-team-card.meta.json +7 -0
  48. package/server/blocks/domma-block-testimonial.css +13 -0
  49. package/server/blocks/domma-block-testimonial.html +12 -0
  50. package/server/blocks/domma-block-testimonial.meta.json +7 -0
  51. package/server/blocks/domma-block-text.css +9 -0
  52. package/server/blocks/domma-block-text.html +11 -0
  53. package/server/blocks/domma-block-text.meta.json +7 -0
  54. package/server/services/blocks.js +2 -0
  55. package/server/services/markdown.js +25 -3
  56. package/server/services/starter-blocks.js +18 -0
@@ -0,0 +1,15 @@
1
+ .db-hero {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ padding: clamp(2rem, 6vw, 5rem) 1.25rem; text-align: center;
6
+ }
7
+ .db-hero-heading { margin: 0 0 1rem; color: inherit; font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
8
+ .db-hero-blurb { margin: 0; line-height: 1.75; white-space: pre-line; }
9
+ .db-hero-blurb:empty { display: none; }
10
+ .db-hero-heading { max-width: 54rem; margin-inline: auto; font-size: clamp(2rem, 5vw, 4rem); }
11
+ .db-hero-blurb { max-width: 42rem; margin-inline: auto; font-size: 1.15rem; }
12
+ .db-hero a.db-hero-link { display: inline-block; max-width: 100%; box-sizing: border-box; margin-top: 1.5rem; padding: .8rem 1.25rem; border: 2px solid currentColor; border-radius: var(--dm-radius, .5rem); color: inherit; font-weight: 600; text-decoration: none; line-height: 1.4; }
13
+ .db-hero a.db-hero-link:hover { text-decoration: underline; }
14
+ .db-hero a.db-hero-link:focus-visible { outline: 3px solid currentColor; outline-offset: 4px; }
15
+ .db-hero a.db-hero-link[href=""], .db-hero a.db-hero-link:not([href]), .db-hero a.db-hero-link:empty { display: none; }
@@ -0,0 +1,13 @@
1
+ <!--
2
+ domma-block-hero - A centred page introduction with an optional button.
3
+
4
+ Parameters:
5
+ header - Main page heading (h1; use once per page)
6
+ blurb - Short introduction, as plain text
7
+ link - Optional button destination
8
+ label - Optional button text
9
+
10
+ Example:
11
+ [domma-block-hero header="Welcome to your next chapter" blurb="Say what you do and who you help." link="/contact" label="Let us help" /]
12
+ -->
13
+ <section class="db-hero"><h1 class="db-hero-heading">{{header}}</h1><p class="db-hero-blurb">{{blurb}}</p><a class="db-hero-link db-hero-btn" href="{{link}}">{{label}}</a></section>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "introductions"
6
+ ]
7
+ }
@@ -0,0 +1,8 @@
1
+ .db-image {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ }
6
+ .db-image img { display: block; width: 100%; height: auto; border-radius: var(--dm-radius-xl, 1rem); }
7
+ .db-image figcaption { margin-top: .75rem; font-size: .95rem; line-height: 1.6; text-align: center; white-space: pre-line; }
8
+ .db-image img[src=""], .db-image img:not([src]), .db-image figcaption:empty { display: none; }
@@ -0,0 +1,12 @@
1
+ <!--
2
+ domma-block-image - An uncropped image with an optional caption.
3
+
4
+ Parameters:
5
+ image - Image address from your Media library
6
+ alt - Image description; empty for a decorative image
7
+ caption - Optional image caption, as plain text
8
+
9
+ Example:
10
+ [domma-block-image image="/media/your-work.jpg" alt="Describe what visitors can see" caption="Add a little context to the image." /]
11
+ -->
12
+ <figure class="db-image"><img src="{{image}}" alt="{{alt}}" loading="lazy" decoding="async"><figcaption>{{caption}}</figcaption></figure>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "content"
6
+ ]
7
+ }
@@ -0,0 +1,13 @@
1
+ .db-logos {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ text-align: center;
6
+ }
7
+ .db-logos-heading { margin: 0 0 1rem; color: inherit; font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
8
+ .db-logos-blurb { margin: 0; line-height: 1.75; white-space: pre-line; }
9
+ .db-logos-blurb:empty { display: none; }
10
+ .db-logos-heading:empty { display: none; }
11
+ .db-logos-grid { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2rem; padding: 1.5rem; }
12
+ .db-logos-grid img { display: block; width: 10rem; max-width: 100%; height: 5rem; object-fit: contain; }
13
+ .db-logos-grid img[src=""], .db-logos-grid img:not([src]) { display: none; }
@@ -0,0 +1,18 @@
1
+ <!--
2
+ domma-block-logos - A responsive row of up to four organisation logos.
3
+
4
+ Parameters:
5
+ header - Optional heading (h2)
6
+ image1 - Logo 1 image address
7
+ alt1 - Logo 1 organisation name
8
+ image2 - Logo 2 image address
9
+ alt2 - Logo 2 organisation name
10
+ image3 - Logo 3 image address
11
+ alt3 - Logo 3 organisation name
12
+ image4 - Logo 4 image address
13
+ alt4 - Logo 4 organisation name
14
+
15
+ Example:
16
+ [domma-block-logos header="Who we work with" image1="/media/partner-logo.svg" alt1="Partner name" /]
17
+ -->
18
+ <section class="db-logos"><h2 class="db-logos-heading">{{header}}</h2><div class="db-logos-grid"><img src="{{image1}}" alt="{{alt1}}" loading="lazy" decoding="async"><img src="{{image2}}" alt="{{alt2}}" loading="lazy" decoding="async"><img src="{{image3}}" alt="{{alt3}}" loading="lazy" decoding="async"><img src="{{image4}}" alt="{{alt4}}" loading="lazy" decoding="async"></div></section>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "trust"
6
+ ]
7
+ }
@@ -0,0 +1,20 @@
1
+ .db-price {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ padding: clamp(1.25rem, 3vw, 2rem);
6
+ border: 1px solid var(--dm-card-border, var(--dm-border, #d5e0e5));
7
+ border-radius: var(--dm-radius-xl, 1rem);
8
+ background: var(--dm-card-bg, var(--dm-surface, #f5f8fa));
9
+ }
10
+ .db-price-heading { margin: 0 0 1rem; color: inherit; font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
11
+ .db-price-blurb { margin: 0; line-height: 1.75; white-space: pre-line; }
12
+ .db-price-blurb:empty { display: none; }
13
+ .db-price-heading { font-size: 1.4rem; }
14
+ .db-price-amount { margin: 0 0 .5rem; font-size: clamp(2rem, 4vw, 3rem); font-weight: 700; line-height: 1.2; }
15
+ .db-price-period { margin: 0 0 1.5rem; line-height: 1.6; }
16
+ .db-price-period:empty { display: none; }
17
+ .db-price a.db-price-link { display: inline-block; max-width: 100%; box-sizing: border-box; margin-top: 1.5rem; padding: .8rem 1.25rem; border: 2px solid currentColor; border-radius: var(--dm-radius, .5rem); color: inherit; font-weight: 600; text-decoration: none; line-height: 1.4; }
18
+ .db-price a.db-price-link:hover { text-decoration: underline; }
19
+ .db-price a.db-price-link:focus-visible { outline: 3px solid currentColor; outline-offset: 4px; }
20
+ .db-price a.db-price-link[href=""], .db-price a.db-price-link:not([href]), .db-price a.db-price-link:empty { display: none; }
@@ -0,0 +1,15 @@
1
+ <!--
2
+ domma-block-pricing-card - A clear price or package card with an optional enquiry button.
3
+
4
+ Parameters:
5
+ header - Package heading (h3)
6
+ blurb - What is included, as plain text
7
+ price - Price text, including currency and any tax wording
8
+ period - Optional billing period or pricing note
9
+ link - Optional button destination
10
+ label - Optional button text
11
+
12
+ Example:
13
+ [domma-block-pricing-card header="Your package" price="Ask for a quote" blurb="Describe exactly what is included." link="/contact" label="Talk it through" /]
14
+ -->
15
+ <article class="db-price"><h3 class="db-price-heading">{{header}}</h3><p class="db-price-amount">{{price}}</p><p class="db-price-period">{{period}}</p><p class="db-price-blurb">{{blurb}}</p><a class="db-price-link db-price-btn" href="{{link}}">{{label}}</a></article>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "services"
6
+ ]
7
+ }
@@ -0,0 +1,17 @@
1
+ .db-service {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ overflow: hidden; border: 1px solid var(--dm-card-border, var(--dm-border, #d5e0e5)); border-radius: var(--dm-radius-xl, 1rem); background: var(--dm-card-bg, var(--dm-surface, #f5f8fa));
6
+ }
7
+ .db-service-image { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
8
+ .db-service-image[src=""], .db-service-image:not([src]) { display: none; }
9
+ .db-service-body { padding: 1.5rem; }
10
+ .db-service-heading { margin: 0 0 1rem; color: inherit; font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
11
+ .db-service-blurb { margin: 0; line-height: 1.75; white-space: pre-line; }
12
+ .db-service-blurb:empty { display: none; }
13
+ .db-service-heading { font-size: 1.4rem; }
14
+ .db-service a.db-service-link { display: inline-block; max-width: 100%; box-sizing: border-box; margin-top: 1.5rem; padding: .8rem 1.25rem; border: 2px solid currentColor; border-radius: var(--dm-radius, .5rem); color: inherit; font-weight: 600; text-decoration: none; line-height: 1.4; }
15
+ .db-service a.db-service-link:hover { text-decoration: underline; }
16
+ .db-service a.db-service-link:focus-visible { outline: 3px solid currentColor; outline-offset: 4px; }
17
+ .db-service a.db-service-link[href=""], .db-service a.db-service-link:not([href]), .db-service a.db-service-link:empty { display: none; }
@@ -0,0 +1,15 @@
1
+ <!--
2
+ domma-block-service-card - A reusable service card with an optional photo and button.
3
+
4
+ Parameters:
5
+ header - Card heading (h3)
6
+ blurb - Service description, as plain text
7
+ image - Image address from your Media library
8
+ alt - Image description; empty for a decorative image
9
+ link - Optional button destination
10
+ label - Optional button text
11
+
12
+ Example:
13
+ [domma-block-service-card header="Your service" blurb="Explain the benefit in a sentence or two." link="/services" label="Find out more" /]
14
+ -->
15
+ <article class="db-service"><img class="db-service-image" src="{{image}}" alt="{{alt}}" loading="lazy" decoding="async"><div class="db-service-body"><h3 class="db-service-heading">{{header}}</h3><p class="db-service-blurb">{{blurb}}</p><a class="db-service-link db-service-btn" href="{{link}}">{{label}}</a></div></article>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "services"
6
+ ]
7
+ }
@@ -0,0 +1,26 @@
1
+ .db-split {
2
+ margin-block: 1.5rem;
3
+ display: grid;
4
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
5
+ align-items: center;
6
+ gap: clamp(1.5rem, 4vw, 3.5rem);
7
+ color: var(--dm-text, #152832);
8
+ }
9
+ .db-split-image {
10
+ display: block;
11
+ width: 100%;
12
+ aspect-ratio: 4 / 3;
13
+ object-fit: cover;
14
+ border-radius: var(--dm-radius-xl, 1rem);
15
+ }
16
+ .db-split-image[src=""], .db-split-image:not([src]) { display: none; }
17
+ .db-split-body { min-width: 0; padding: .5rem 0; }
18
+ .db-split-heading { margin: 0 0 1rem; font-size: clamp(1.75rem, 3vw, 2.5rem); line-height: 1.2; color: inherit; overflow-wrap: anywhere; }
19
+ .db-split-blurb { margin: 0; font-size: 1.1rem; line-height: 1.75; white-space: pre-line; overflow-wrap: anywhere; }
20
+ .db-split a.db-split-link { display: inline-block; margin-top: 1.5rem; padding: .8rem 1.25rem; border: 2px solid currentColor; border-radius: var(--dm-radius, .5rem); color: inherit; font-weight: 600; text-decoration: none; overflow-wrap: anywhere; }
21
+ .db-split a.db-split-link:hover { text-decoration: underline; }
22
+ .db-split a.db-split-link:focus-visible { outline: 3px solid currentColor; outline-offset: 4px; }
23
+ .db-split a.db-split-link[href=""], .db-split a.db-split-link:not([href]), .db-split a.db-split-link:empty { display: none; }
24
+ @media (max-width: 767px) {
25
+ .db-split { grid-template-columns: minmax(0, 1fr); }
26
+ }
@@ -0,0 +1,22 @@
1
+ <!--
2
+ domma-block-split - A simple image and text section that stacks on phones.
3
+
4
+ Parameters:
5
+ header - Section heading (h2)
6
+ blurb - Section introduction, as plain text
7
+ image - Image address from your media library
8
+ alt - Description of the image
9
+ link - Optional button destination
10
+ label - Optional button text
11
+
12
+ Example:
13
+ [domma-block-split header="Made for your business" blurb="Tell visitors how you can help." image="/media/about.jpg" alt="Our workspace" link="/about" label="Meet us" /]
14
+ -->
15
+ <section class="db-split">
16
+ <img class="db-split-image" src="{{image}}" alt="{{alt}}" loading="lazy" decoding="async">
17
+ <div class="db-split-body">
18
+ <h2 class="db-split-heading">{{header}}</h2>
19
+ <p class="db-split-blurb">{{blurb}}</p>
20
+ <a class="db-split-link db-split-btn" href="{{link}}">{{label}}</a>
21
+ </div>
22
+ </section>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "content"
6
+ ]
7
+ }
@@ -0,0 +1,20 @@
1
+ .db-stats {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ text-align: center;
6
+ }
7
+ .db-stats-heading { margin: 0 0 1rem; color: inherit; font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
8
+ .db-stats-blurb { margin: 0; line-height: 1.75; white-space: pre-line; }
9
+ .db-stats-blurb:empty { display: none; }
10
+ .db-stats-heading:empty { display: none; }
11
+ .db-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); gap: 1.25rem; margin: 0; }
12
+ .db-stats-item { display: flex; flex-direction: column; gap: .5rem;
13
+ padding: clamp(1.25rem, 3vw, 2rem);
14
+ border: 1px solid var(--dm-card-border, var(--dm-border, #d5e0e5));
15
+ border-radius: var(--dm-radius-xl, 1rem);
16
+ background: var(--dm-card-bg, var(--dm-surface, #f5f8fa));
17
+ }
18
+ .db-stats-item dt { order: 2; font-weight: 400; line-height: 1.6; }
19
+ .db-stats-item dd { order: 1; margin: 0; font-size: clamp(2rem, 4vw, 3rem); font-weight: 700; line-height: 1.2; }
20
+ .db-stats-item:has(dd:empty) { display: none; }
@@ -0,0 +1,16 @@
1
+ <!--
2
+ domma-block-stats - Up to three useful figures with clear labels.
3
+
4
+ Parameters:
5
+ header - Optional section heading (h2)
6
+ value1 - Figure 1, including any unit
7
+ label1 - Figure 1 explanation
8
+ value2 - Figure 2, including any unit
9
+ label2 - Figure 2 explanation
10
+ value3 - Figure 3, including any unit
11
+ label3 - Figure 3 explanation
12
+
13
+ Example:
14
+ [domma-block-stats header="A few useful facts" value1="Your figure" label1="Explain what it represents" /]
15
+ -->
16
+ <section class="db-stats"><h2 class="db-stats-heading">{{header}}</h2><dl class="db-stats-grid"><div class="db-stats-item"><dt>{{label1}}</dt><dd>{{value1}}</dd></div><div class="db-stats-item"><dt>{{label2}}</dt><dd>{{value2}}</dd></div><div class="db-stats-item"><dt>{{label3}}</dt><dd>{{value3}}</dd></div></dl></section>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "trust"
6
+ ]
7
+ }
@@ -0,0 +1,19 @@
1
+ .db-steps {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ }
6
+ .db-steps-heading { margin: 0 0 1rem; color: inherit; font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
7
+ .db-steps-blurb { margin: 0; line-height: 1.75; white-space: pre-line; }
8
+ .db-steps-blurb:empty { display: none; }
9
+ .db-steps-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); gap: 1.25rem; counter-reset: db-step; list-style: none; margin: 1.5rem 0 0; padding: 0; }
10
+ .db-steps-item { counter-increment: db-step;
11
+ padding: clamp(1.25rem, 3vw, 2rem);
12
+ border: 1px solid var(--dm-card-border, var(--dm-border, #d5e0e5));
13
+ border-radius: var(--dm-radius-xl, 1rem);
14
+ background: var(--dm-card-bg, var(--dm-surface, #f5f8fa));
15
+ }
16
+ .db-steps-item::before { content: counter(db-step, decimal-leading-zero); display: block; margin-bottom: 1rem; font-size: 1.75rem; font-weight: 700; }
17
+ .db-steps-item h3 { margin: 0 0 .75rem; color: inherit; font-size: 1.25rem; }
18
+ .db-steps-item p { margin: 0; line-height: 1.7; white-space: pre-line; }
19
+ .db-steps-item:has(h3:empty) { display: none; }
@@ -0,0 +1,17 @@
1
+ <!--
2
+ domma-block-steps - A clear, numbered process with up to three steps.
3
+
4
+ Parameters:
5
+ header - Section heading (h2)
6
+ blurb - Introduction, as plain text
7
+ title1 - Step 1 heading
8
+ text1 - Step 1 description
9
+ title2 - Step 2 heading
10
+ text2 - Step 2 description
11
+ title3 - Step 3 heading
12
+ text3 - Step 3 description
13
+
14
+ Example:
15
+ [domma-block-steps header="Getting started" blurb="A simple way forward." title1="Say hello" text1="Tell us what you need." title2="Make a plan" text2="Agree the details together." title3="Get going" text3="Put your plan into action." /]
16
+ -->
17
+ <section class="db-steps"><h2 class="db-steps-heading">{{header}}</h2><p class="db-steps-blurb">{{blurb}}</p><ol class="db-steps-list"><li class="db-steps-item"><h3>{{title1}}</h3><p>{{text1}}</p></li><li class="db-steps-item"><h3>{{title2}}</h3><p>{{text2}}</p></li><li class="db-steps-item"><h3>{{title3}}</h3><p>{{text3}}</p></li></ol></section>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "services"
6
+ ]
7
+ }
@@ -0,0 +1,13 @@
1
+ .db-team {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ border: 1px solid var(--dm-card-border, var(--dm-border, #d5e0e5)); border-radius: var(--dm-radius-xl, 1rem); overflow: hidden; background: var(--dm-card-bg, var(--dm-surface, #f5f8fa));
6
+ }
7
+ .db-team-image { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; }
8
+ .db-team-image[src=""], .db-team-image:not([src]) { display: none; }
9
+ .db-team-body { padding: 1.5rem; }
10
+ .db-team-name { margin: 0 0 .5rem; font-size: 1.4rem; color: inherit; }
11
+ .db-team-role { margin: 0 0 1rem; font-weight: 600; }
12
+ .db-team-blurb { margin: 0; white-space: pre-line; line-height: 1.7; }
13
+ .db-team-role:empty, .db-team-blurb:empty { display: none; }
@@ -0,0 +1,14 @@
1
+ <!--
2
+ domma-block-team-card - A team member photo, role and short introduction.
3
+
4
+ Parameters:
5
+ name - Person name (h3)
6
+ role - Role or specialism
7
+ blurb - Short introduction, as plain text
8
+ image - Image address from your Media library
9
+ alt - Image description; empty for a decorative image
10
+
11
+ Example:
12
+ [domma-block-team-card name="Team member" role="Role or specialism" blurb="A short, friendly introduction." image="/media/team-member.jpg" alt="Portrait of your team member" /]
13
+ -->
14
+ <article class="db-team"><img class="db-team-image" src="{{image}}" alt="{{alt}}" loading="lazy" decoding="async"><div class="db-team-body"><h3 class="db-team-name">{{name}}</h3><p class="db-team-role">{{role}}</p><p class="db-team-blurb">{{blurb}}</p></div></article>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "trust"
6
+ ]
7
+ }
@@ -0,0 +1,13 @@
1
+ .db-testimonial {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ padding: clamp(1.25rem, 3vw, 2rem);
6
+ border: 1px solid var(--dm-card-border, var(--dm-border, #d5e0e5));
7
+ border-radius: var(--dm-radius-xl, 1rem);
8
+ background: var(--dm-card-bg, var(--dm-surface, #f5f8fa));
9
+ }
10
+ .db-testimonial-quote { margin: 0; padding: 0; border: 0; color: inherit; font-size: clamp(1.15rem, 2vw, 1.6rem); line-height: 1.65; white-space: pre-line; }
11
+ .db-testimonial-attribution { display: grid; gap: .3rem; margin-top: 1.25rem; }
12
+ .db-testimonial-attribution span { font-size: .95rem; }
13
+ .db-testimonial-attribution strong:empty, .db-testimonial-attribution span:empty, .db-testimonial-attribution:has(strong:empty):has(span:empty) { display: none; }
@@ -0,0 +1,12 @@
1
+ <!--
2
+ domma-block-testimonial - A customer quotation with an optional attribution.
3
+
4
+ Parameters:
5
+ quote - An approved customer quotation, as plain text
6
+ name - Customer name
7
+ detail - Optional role or business
8
+
9
+ Example:
10
+ [domma-block-testimonial quote="Replace this with a customer quotation you have permission to share." name="Customer name" detail="Business or role" /]
11
+ -->
12
+ <figure class="db-testimonial"><blockquote class="db-testimonial-quote">{{quote}}</blockquote><figcaption class="db-testimonial-attribution"><strong>{{name}}</strong><span>{{detail}}</span></figcaption></figure>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "trust"
6
+ ]
7
+ }
@@ -0,0 +1,9 @@
1
+ .db-text {
2
+ margin-block: 1.5rem;
3
+ color: var(--dm-text, #152832);
4
+ overflow-wrap: anywhere;
5
+ max-width: 48rem; margin-inline: auto; padding-block: 1rem;
6
+ }
7
+ .db-text-heading { margin: 0 0 1rem; color: inherit; font-size: clamp(1.6rem, 3vw, 2.4rem); line-height: 1.2; }
8
+ .db-text-blurb { margin: 0; line-height: 1.75; white-space: pre-line; }
9
+ .db-text-blurb:empty { display: none; }
@@ -0,0 +1,11 @@
1
+ <!--
2
+ domma-block-text - A readable text section for your story or an introduction.
3
+
4
+ Parameters:
5
+ header - Section heading (h2)
6
+ blurb - Introduction, as plain text
7
+
8
+ Example:
9
+ [domma-block-text header="Our story" blurb="Share a little about your business and the people behind it." /]
10
+ -->
11
+ <section class="db-text"><h2 class="db-text-heading">{{header}}</h2><p class="db-text-blurb">{{blurb}}</p></section>
@@ -0,0 +1,7 @@
1
+ {
2
+ "bundled": true,
3
+ "adminTags": [
4
+ "domma-blocks",
5
+ "content"
6
+ ]
7
+ }
@@ -7,6 +7,7 @@ import fs from 'fs/promises';
7
7
  import path from 'path';
8
8
  import {fileURLToPath} from 'url';
9
9
  import * as cache from './cache/index.js';
10
+ import {seedStarterBlocks} from './starter-blocks.js';
10
11
 
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
  const BLOCKS_DIR = path.resolve(__dirname, '../../content/blocks');
@@ -427,6 +428,7 @@ export async function deleteBlock(name) {
427
428
  * Skips any file that already exists to preserve user customisations.
428
429
  */
429
430
  export async function seedDefaultBlocks() {
431
+ await seedStarterBlocks(BLOCKS_DIR);
430
432
  for (const [name, content] of Object.entries(BLOCKS)) {
431
433
  await seedBlock(name, content);
432
434
  }
@@ -423,10 +423,29 @@ export function buildBlockStyleTag(blockName, css) {
423
423
  * @param {object} e - Collection entry { id, data, meta }
424
424
  * @returns {string}
425
425
  */
426
+ /**
427
+ * A filled block goes into Markdown as HTML, and a blank line ends an HTML block:
428
+ * what follows, indented, marked then shows as code - raw tags on the page. A
429
+ * placeholder alone on its line with nothing to fill it (` {{bio}}` with no
430
+ * bio) made exactly that blank line, so such a line goes. The template's OWN
431
+ * blank lines stay: they are how a template hands a Markdown value to marked.
432
+ * Each line is filled by itself, in one pass as before.
433
+ */
434
+ function fillTemplateLines(template, fill) {
435
+ const PLACEHOLDER = /\{\{\{(\w+)\}\}\}|\{\{([\w_]+)(?::([^{}]+))?\}\}/g;
436
+ const out = [];
437
+ for (const line of template.split('\n')) {
438
+ const filled = line.replace(PLACEHOLDER, fill);
439
+ if (!filled.trim() && line.trim() && /\{\{/.test(line)) continue;
440
+ out.push(filled);
441
+ }
442
+ return out.join('\n');
443
+ }
444
+
426
445
  function interpolateBlockTemplate(blockTemplate, e) {
427
446
  // One pass: a raw value put in (pre-rendered HTML) is never read again for
428
447
  // placeholders - text that shows "{{title}}" keeps it.
429
- return blockTemplate.replace(/\{\{\{(\w+)\}\}\}|\{\{([\w_]+)(?::([^{}]+))?\}\}/g, (_, raw, key, fmt) => {
448
+ return fillTemplateLines(blockTemplate, (_, raw, key, fmt) => {
430
449
  // Entry values never carry a shortcode into the page (inertShortcodes);
431
450
  // the template's own markup is untouched.
432
451
  if (raw !== undefined) return inertShortcodes(String(e.data?.[raw] ?? ''));
@@ -510,7 +529,7 @@ function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts, cols,
510
529
  function renderBlockFromAttrs(tmpl, attrs, blockName = '') {
511
530
  // Triple-brace raw pass-through and `{{field}}` (optional `:format`, e.g. `{{date:DD MMM YYYY}}`)
512
531
  // in one pass, so a raw value is never read again for placeholders.
513
- const rendered = tmpl.replace(/\{\{\{(\w+)\}\}\}|\{\{([\w_]+)(?::([^{}]+))?\}\}/g, (_, rawKey, key, fmt) => {
532
+ const rendered = fillTemplateLines(tmpl, (_, rawKey, key, fmt) => {
514
533
  if (rawKey !== undefined) return String(attrs[rawKey] ?? '');
515
534
  const raw = attrs[key] ?? '';
516
535
  if (fmt) {
@@ -1967,7 +1986,10 @@ export function scrubCodeRegions(markdown, preserveIndented = false) {
1967
1986
  // Inline code spans (single or multiple backticks, non-greedy)
1968
1987
  scrubbed = scrubbed.replace(/`+[^`\n]+`+/g, placeholder);
1969
1988
 
1970
- const restore = (s) => s.replace(new RegExp(`\\x02SC${callId}:(\\d+)\\x03`, 'g'), (_, i) => store[parseInt(i, 10)]);
1989
+ // A stored region can hold an earlier placeholder of this same call - an indented
1990
+ // line (preserveIndented) holding an inline <code> - so what comes back is
1991
+ // restored too. Restoring it once left "SC1:0" on the page.
1992
+ const restore = (s) => s.replace(new RegExp(`\\x02SC${callId}:(\\d+)\\x03`, 'g'), (_, i) => restore(store[parseInt(i, 10)]));
1971
1993
  return {scrubbed, restore};
1972
1994
  }
1973
1995
 
@@ -0,0 +1,18 @@
1
+ /** Editable starting points for new pages, copied without replacing site edits. */
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+
6
+ const sourceDir = fileURLToPath(new URL('../blocks/', import.meta.url));
7
+
8
+ export async function seedStarterBlocks(destination) {
9
+ await fs.mkdir(destination, {recursive: true});
10
+ for (const name of await fs.readdir(sourceDir)) {
11
+ if (!/^domma-block-[a-z-]+\.(html|css|meta\.json)$/.test(name)) continue;
12
+ try {
13
+ await fs.copyFile(path.join(sourceDir, name), path.join(destination, name), fs.constants.COPYFILE_EXCL);
14
+ } catch (error) {
15
+ if (error.code !== 'EEXIST') throw error;
16
+ }
17
+ }
18
+ }