@port60/template-kit 0.21.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -3
- package/bin/cli.mjs +10 -0
- package/package.json +4 -2
- package/src/commands/content.mjs +4 -3
- package/src/commands/create.mjs +11 -3
- package/src/commands/dev.mjs +9 -3
- package/src/commands/model.mjs +3 -3
- package/src/commands/packageCmd.mjs +1 -1
- package/src/commands/publish.mjs +1 -1
- package/src/commands/refresh.mjs +8 -3
- package/src/commands/release.mjs +22 -0
- package/src/commands/setupPreviews.mjs +19 -0
- package/src/commands/validate.mjs +1 -1
- package/src/lib/agentsMd.mjs +108 -162
- package/src/lib/designer-bridge.js +26 -0
- package/src/lib/designerPalette.mjs +62 -0
- package/src/lib/galleryPosters.mjs +105 -0
- package/src/lib/previewOptions.mjs +1 -1
- package/src/lib/releaseBundle.mjs +141 -0
- package/src/vendor/contract/v2/behaviours.json +346 -0
- package/src/vendor/contract/v2/content-bounds.json +117 -0
- package/src/vendor/contract/v2/content-model.json +766 -0
- package/src/vendor/contract/v2/context.json +1569 -0
- package/src/vendor/contract/v2/dialect.json +105 -0
- package/src/vendor/contract/v2/fonts.json +910 -0
- package/src/vendor/contract/v2/imagery.json +32 -0
- package/src/vendor/contract/v2/islands.json +263 -0
- package/src/vendor/contract/v2/layout.json +28 -0
- package/src/vendor/contract/v2/manifest.schema.json +388 -0
- package/src/vendor/contract/v2/sections.json +947 -0
- package/src/vendor/contract/v2/site.schema.json +1353 -0
- package/src/vendor/contract/v2/tokens.json +101 -0
- package/src/vendor/contract/v2.lock.json +6931 -0
- package/src/vendor/engine/content-footprint.mjs +76 -1
- package/src/vendor/engine/majors.mjs +42 -0
- package/src/vendor/validator/gift-aid-logo.svg +6 -0
- package/src/vendor/validator/model-reference-v2.mjs +95 -0
- package/src/vendor/validator/platform-base.css +310 -0
- package/src/vendor/validator/preview-v1.mjs +691 -0
- package/src/vendor/validator/preview-v2.mjs +654 -0
- package/src/vendor/validator/preview.mjs +8 -688
- package/src/vendor/validator/site-context-v2.mjs +113 -0
- package/src/vendor/validator/validate-v1.mjs +666 -0
- package/src/vendor/validator/validate-v2.mjs +642 -0
- package/src/vendor/validator/validate.mjs +21 -663
- package/starter/assets/theme.css +3 -0
- package/starter/layout.liquid +15 -18
- package/starter/manifest.json +5 -4
- package/starter/preview/config.json +4 -0
- package/starter/preview/media/README.md +9 -0
- package/starter/sections/campaigns.liquid +5 -4
- package/starter/sections/homeHero.liquid +25 -21
- package/starter/sections/values.liquid +1 -1
package/starter/assets/theme.css
CHANGED
|
@@ -25,6 +25,9 @@ h3 { font-size: 1.15rem; }
|
|
|
25
25
|
p { color: var(--muted); line-height: 1.7; margin: 0 0 1rem; }
|
|
26
26
|
strong { color: var(--ink); }
|
|
27
27
|
.site-header { background: rgba(14, 22, 20, 0.86); }
|
|
28
|
+
/* Non-link groups keep the same typography and hit area as neighbouring navigation links. */
|
|
29
|
+
.site-nav button.nav-dropdown-toggle { appearance: none; border: 0; background: transparent; padding: 0.6rem 0.95rem; border-radius: 999px; color: var(--muted); font: inherit; font-weight: 600; cursor: pointer; }
|
|
30
|
+
.site-nav button.nav-dropdown-toggle:hover { background: rgba(31, 42, 51, 0.05); color: var(--ink); }
|
|
28
31
|
.lq-hero { padding: 4.5rem 0 2.5rem; border-bottom: 1px solid var(--line); }
|
|
29
32
|
.lq-kicker { display: inline-block; border: 1px solid var(--line-strong); color: var(--primary); border-radius: 4px; padding: 0.3rem 0.8rem; font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 700; }
|
|
30
33
|
.lq-hero h1 { margin-top: 1rem; max-width: 18ch; }
|
package/starter/layout.liquid
CHANGED
|
@@ -25,23 +25,25 @@
|
|
|
25
25
|
<span aria-hidden="true"></span>
|
|
26
26
|
</button>
|
|
27
27
|
|
|
28
|
-
<nav class="site-nav" id="site-nav" aria-label="Primary">
|
|
29
|
-
{% for item in site.nav.
|
|
30
|
-
{% if item.children %}
|
|
31
|
-
<span class="nav-dropdown">
|
|
32
|
-
<a href="{{ item.href }}" class="nav-dropdown-toggle" aria-
|
|
33
|
-
<span class="nav-dropdown-menu">
|
|
28
|
+
<nav class="site-nav" id="site-nav" aria-label="Primary" data-p60-nav>
|
|
29
|
+
{% for item in site.nav.header %}
|
|
30
|
+
{% if item.children.size > 0 %}
|
|
31
|
+
<span class="nav-dropdown" data-p60-nav-item>
|
|
32
|
+
{% if item.kind == 'group' %}<button type="button" class="nav-dropdown-toggle" data-p60-nav-toggle aria-expanded="false" aria-controls="starter-menu-{{ forloop.index }}">{{ item.label }} ▾</button>{% else %}<a href="{{ item.href }}" class="nav-dropdown-toggle" data-p60-nav-toggle aria-expanded="false" aria-controls="starter-menu-{{ forloop.index }}"{% if item.external %} target="_blank" rel="noopener"{% endif %}>{{ item.label }} ▾</a>{% endif %}
|
|
33
|
+
<span class="nav-dropdown-menu" id="starter-menu-{{ forloop.index }}" data-p60-nav-menu>
|
|
34
34
|
{% for child in item.children %}
|
|
35
|
-
<a href="{{ child.href }}"{% if child.external %} target="_blank" rel="noopener"{% endif %}>{{ child.label }}</a>
|
|
35
|
+
{% if child.kind == 'group' %}<span class="nav-group-label">{{ child.label }}</span>{% else %}<a href="{{ child.href }}"{% if child.external %} target="_blank" rel="noopener"{% endif %}>{{ child.label }}</a>{% endif %}
|
|
36
|
+
{% for grandchild in child.children %}{% if grandchild.kind == 'group' %}<span class="nav-group-label">{{ grandchild.label }}</span>{% else %}<a href="{{ grandchild.href }}"{% if grandchild.external %} target="_blank" rel="noopener"{% endif %}>{{ grandchild.label }}</a>{% endif %}{% endfor %}
|
|
36
37
|
{% endfor %}
|
|
37
38
|
</span>
|
|
38
39
|
</span>
|
|
39
|
-
{% elsif item.
|
|
40
|
-
<
|
|
40
|
+
{% elsif item.kind == 'group' %}
|
|
41
|
+
<span class="nav-group-label">{{ item.label }}</span>
|
|
41
42
|
{% else %}
|
|
42
43
|
<a href="{{ item.href }}"{% if item.external %} target="_blank" rel="noopener"{% endif %}>{{ item.label }}</a>
|
|
43
44
|
{% endif %}
|
|
44
45
|
{% endfor %}
|
|
46
|
+
{% if site.actions.header %}<a class="nav-cta" href="{{ site.actions.header.href }}">{{ site.actions.header.label }}</a>{% endif %}
|
|
45
47
|
{% island 'search' %}
|
|
46
48
|
{% island 'member_menu' %}
|
|
47
49
|
</nav>
|
|
@@ -59,19 +61,14 @@
|
|
|
59
61
|
{% if site.brand.tagline %}<h2>{{ site.brand.tagline }}</h2>{% endif %}
|
|
60
62
|
</div>
|
|
61
63
|
<div class="footer-links">
|
|
62
|
-
{% comment %}
|
|
63
|
-
{% if site.nav.footer.size > 0 %}
|
|
64
|
+
{% comment %} The platform resolves the footer independently; an empty footer stays empty. {% endcomment %}
|
|
64
65
|
{% for item in site.nav.footer %}
|
|
65
|
-
<a href="{{ item.href }}"{% if item.external %} target="_blank" rel="noopener"{% endif %}>{{ item.label }}</a>
|
|
66
|
+
{% if item.kind == 'group' %}<span class="nav-group-label">{{ item.label }}</span>{% else %}<a href="{{ item.href }}"{% if item.external %} target="_blank" rel="noopener"{% endif %}>{{ item.label }}</a>{% endif %}
|
|
66
67
|
{% for child in item.children %}
|
|
67
|
-
<a href="{{ child.href }}"{% if child.external %} target="_blank" rel="noopener"{% endif %}>{{ child.label }}</a>
|
|
68
|
+
{% if child.kind == 'group' %}<span class="nav-group-label">{{ child.label }}</span>{% else %}<a href="{{ child.href }}"{% if child.external %} target="_blank" rel="noopener"{% endif %}>{{ child.label }}</a>{% endif %}
|
|
69
|
+
{% for grandchild in child.children %}{% if grandchild.kind == 'group' %}<span class="nav-group-label">{{ grandchild.label }}</span>{% else %}<a href="{{ grandchild.href }}">{{ grandchild.label }}</a>{% endif %}{% endfor %}
|
|
68
70
|
{% endfor %}
|
|
69
71
|
{% endfor %}
|
|
70
|
-
{% else %}
|
|
71
|
-
{% for item in site.nav.items %}
|
|
72
|
-
<a href="{{ item.href }}">{{ item.label }}</a>
|
|
73
|
-
{% endfor %}
|
|
74
|
-
{% endif %}
|
|
75
72
|
</div>
|
|
76
73
|
</div>
|
|
77
74
|
<div class="container">
|
package/starter/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "starter",
|
|
3
|
-
"version": "
|
|
4
|
-
"format": "port60-liquid@
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"format": "port60-liquid@2",
|
|
5
5
|
"label": "Starter",
|
|
6
6
|
"description": "The reference template for the Port60 dialect, the developer docs' worked example and the base to copy when building your own. 1.1.0 adds the layout slot: the template owns the header, navigation and footer chrome.",
|
|
7
7
|
"supports": {
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"heroImagery": true,
|
|
33
33
|
"behaviors": [
|
|
34
34
|
"reveal",
|
|
35
|
-
"carousel"
|
|
35
|
+
"carousel",
|
|
36
|
+
"nav"
|
|
36
37
|
],
|
|
37
38
|
"focus": [
|
|
38
39
|
"donate",
|
|
@@ -94,7 +95,7 @@
|
|
|
94
95
|
}
|
|
95
96
|
]
|
|
96
97
|
},
|
|
97
|
-
"changelog": "
|
|
98
|
+
"changelog": "Uses the v2 site object, resolved header actions and collection envelopes while preserving authored field markers.",
|
|
98
99
|
"imagery": {
|
|
99
100
|
"hero": {
|
|
100
101
|
"idealAspect": "16:9",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Designer preview imagery
|
|
2
|
+
|
|
3
|
+
Place authorised JPEG, PNG or WebP images here, at most 2 MiB each. Reference an image in
|
|
4
|
+
your independent author content as `p60preview:filename.jpg`. Configure that JSON content
|
|
5
|
+
file through `preview/config.json`, for example `"content": "content.json"`.
|
|
6
|
+
|
|
7
|
+
These images ship only in the separate designer preview bundle. They never enter the
|
|
8
|
+
runtime Liquid/CSS artifact or a tenant's media library. Keep provenance and required
|
|
9
|
+
attribution beside your source images. No tenant content or secrets belong here.
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
platform's published site.content.campaigns; hrefs are platform-derived, always link via campaign.href.
|
|
5
5
|
Derive or omit: nothing renders when no campaign is live.
|
|
6
6
|
{% endcomment %}
|
|
7
|
-
{% if site.content.campaigns.size > 0 %}
|
|
7
|
+
{% if site.content.campaigns.items.size > 0 %}
|
|
8
8
|
<section class="lq-site.content.campaigns">
|
|
9
9
|
<div class="container">
|
|
10
|
-
<
|
|
11
|
-
{% if section.
|
|
10
|
+
{% if section.eyebrow != nil and section.eyebrow != '' %}<p class="lq-kicker" data-p60-field="eyebrow">{{ section.eyebrow }}</p>{% endif %}
|
|
11
|
+
{% if section.title == nil %}<h2>{{ site.content.campaigns.label }}</h2>{% elsif section.title != '' %}<h2 data-p60-field="title">{{ section.title }}</h2>{% endif %}
|
|
12
|
+
{% if section.subtitle != nil and section.subtitle != '' %}<p data-p60-field="subtitle">{{ section.subtitle }}</p>{% endif %}
|
|
12
13
|
<ul class="lq-campaign-list">
|
|
13
|
-
{% for campaign in site.content.campaigns %}
|
|
14
|
+
{% for campaign in site.content.campaigns.items %}
|
|
14
15
|
<li class="lq-campaign">
|
|
15
16
|
<h3>{{ campaign.title }}</h3>
|
|
16
17
|
{% if campaign.summary %}<p>{{ campaign.summary }}</p>{% endif %}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
The home opener, and the REFERENCE IMPLEMENTATION of supports.heroImagery. Rules worth
|
|
3
3
|
copying:
|
|
4
4
|
|
|
5
|
-
1. EVERY field is optional.
|
|
5
|
+
1. EVERY field is optional. The host resolves inherited copy; explicit blanks omit; no photos means the
|
|
6
6
|
designed gradient below, never a placeholder image.
|
|
7
7
|
2. Photos are NEVER placed raw. The treatments in theme.css lay scrims built from the palette
|
|
8
8
|
variables (color-mix over var(--bg)/var(--bg-tint)), so any uploaded photograph is toned
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
toward the photo's own shape (height-clamped) so a wide landscape stays entirely visible,
|
|
15
15
|
with your copy stacked after the band.
|
|
16
16
|
{% endcomment %}
|
|
17
|
+
{% assign heroButtonLabel = site.actions.hero.label %}
|
|
18
|
+
{% assign heroButtonHref = site.actions.hero.href %}
|
|
19
|
+
{% if section.primaryLabel != nil %}{% assign heroButtonLabel = section.primaryLabel %}{% endif %}
|
|
20
|
+
{% if section.primaryHref != nil %}{% assign heroButtonHref = section.primaryHref %}{% endif %}
|
|
17
21
|
{% assign heroCount = section.images | size %}
|
|
18
22
|
{% if heroCount == 1 %}
|
|
19
23
|
{% assign heroPhoto = section.images.first.imageUrl %}
|
|
@@ -31,19 +35,19 @@
|
|
|
31
35
|
{% endif %}
|
|
32
36
|
{% assign heroFraming = section.photoFraming | default: 'fill' %}
|
|
33
37
|
{% if heroCount > 0 and heroFraming == 'whole' %}
|
|
34
|
-
{% if section.actionStyle == 'button'
|
|
38
|
+
{% if section.actionStyle == 'button' %}
|
|
35
39
|
<section class="lq-homehero lq-homehero--vista{% if heroCount > 1 %} lq-homehero--carousel{% endif %}">
|
|
36
40
|
<div class="lq-vista">
|
|
37
41
|
<img class="lq-vista-sizer" src="{{ section.images.first.imageUrl }}" alt="{{ section.images.first.alt }}" style="object-position: {{ heroFocus }}" fetchpriority="high" decoding="async">
|
|
38
42
|
{% if heroCount > 1 %}{% island 'hero_carousel' %}{% else %}<div class="lq-vista-scrim" aria-hidden="true"></div>{% endif %}
|
|
39
43
|
<div class="lq-vista-copy">
|
|
40
44
|
<div class="container">
|
|
41
|
-
<p class="lq-kicker" data-p60-field="eyebrow">{{ section.eyebrow
|
|
42
|
-
<h1 data-p60-field="title">{{ section.title
|
|
45
|
+
{% if section.eyebrow != nil and section.eyebrow != '' %}<p class="lq-kicker" data-p60-field="eyebrow">{{ section.eyebrow }}</p>{% endif %}
|
|
46
|
+
{% if section.title != nil and section.title != '' %}<h1 data-p60-field="title">{{ section.title }}</h1>{% endif %}
|
|
43
47
|
{% if section.lead %}<p class="lq-homehero-lead" data-p60-field="lead">{{ section.lead }}</p>{% endif %}
|
|
44
48
|
<div class="lq-homehero-actions">
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
{% if heroButtonLabel != nil and heroButtonLabel != '' and heroButtonHref != nil and heroButtonHref != '' %}<a class="button button-primary" href="{{ heroButtonHref }}">{% if section.primaryLabel != nil %}<span data-p60-field="primaryLabel">{{ section.primaryLabel }}</span>{% else %}{{ heroButtonLabel }}{% endif %}</a>{% endif %}
|
|
50
|
+
{% if section.secondaryLabel != nil and section.secondaryLabel != '' and section.secondaryHref != nil and section.secondaryHref != '' %}<a class="button" href="{{ section.secondaryHref }}"><span data-p60-field="secondaryLabel">{{ section.secondaryLabel }}</span></a>{% endif %}
|
|
47
51
|
</div>
|
|
48
52
|
</div>
|
|
49
53
|
</div>
|
|
@@ -59,18 +63,18 @@
|
|
|
59
63
|
</div>
|
|
60
64
|
<div class="container lq-homehero-inner lq-vista-overlap">
|
|
61
65
|
<div>
|
|
62
|
-
<p class="lq-kicker" data-p60-field="eyebrow">{{ section.eyebrow
|
|
63
|
-
<h1 data-p60-field="title">{{ section.title
|
|
66
|
+
{% if section.eyebrow != nil and section.eyebrow != '' %}<p class="lq-kicker" data-p60-field="eyebrow">{{ section.eyebrow }}</p>{% endif %}
|
|
67
|
+
{% if section.title != nil and section.title != '' %}<h1 data-p60-field="title">{{ section.title }}</h1>{% endif %}
|
|
64
68
|
{% if section.lead %}<p class="lq-homehero-lead" data-p60-field="lead">{{ section.lead }}</p>{% endif %}
|
|
65
69
|
<div class="lq-homehero-actions">
|
|
66
|
-
{% if
|
|
67
|
-
<a class="button" href="{{ section.secondaryHref
|
|
70
|
+
{% if heroButtonLabel != nil and heroButtonLabel != '' and heroButtonHref != nil and heroButtonHref != '' %}<a class="button button-primary" href="{{ heroButtonHref }}">{% if section.primaryLabel != nil %}<span data-p60-field="primaryLabel">{{ section.primaryLabel }}</span>{% else %}{{ heroButtonLabel }}{% endif %}</a>{% endif %}
|
|
71
|
+
{% if section.secondaryLabel != nil and section.secondaryLabel != '' and section.secondaryHref != nil and section.secondaryHref != '' %}<a class="button" href="{{ section.secondaryHref }}"><span data-p60-field="secondaryLabel">{{ section.secondaryLabel }}</span></a>{% endif %}
|
|
68
72
|
</div>
|
|
69
73
|
</div>
|
|
70
|
-
<aside id="donate">
|
|
71
|
-
{%- comment -%}
|
|
74
|
+
{% if site.actions.widget != nil and site.actions.widget != 'none' %}<aside id="donate">
|
|
75
|
+
{%- comment -%} site.actions.widget selects the platform widget. The template places it without changing that choice. {%- endcomment -%}
|
|
72
76
|
{% island 'primary_action_widget' %}
|
|
73
|
-
</aside>
|
|
77
|
+
</aside>{% endif %}
|
|
74
78
|
</div>
|
|
75
79
|
</section>
|
|
76
80
|
{% endif %}
|
|
@@ -94,23 +98,23 @@
|
|
|
94
98
|
{% endif %}
|
|
95
99
|
<div class="container lq-homehero-inner">
|
|
96
100
|
<div>
|
|
97
|
-
<p class="lq-kicker" data-p60-field="eyebrow">{{ section.eyebrow
|
|
98
|
-
<h1 data-p60-field="title">{{ section.title
|
|
101
|
+
{% if section.eyebrow != nil and section.eyebrow != '' %}<p class="lq-kicker" data-p60-field="eyebrow">{{ section.eyebrow }}</p>{% endif %}
|
|
102
|
+
{% if section.title != nil and section.title != '' %}<h1 data-p60-field="title">{{ section.title }}</h1>{% endif %}
|
|
99
103
|
{% if section.lead %}<p class="lq-homehero-lead" data-p60-field="lead">{{ section.lead }}</p>{% endif %}
|
|
100
104
|
<div class="lq-homehero-actions">
|
|
101
|
-
{% if section.actionStyle == 'button'
|
|
102
|
-
{% if
|
|
105
|
+
{% if section.actionStyle == 'button' %}
|
|
106
|
+
{% if heroButtonLabel != nil and heroButtonLabel != '' and heroButtonHref != nil and heroButtonHref != '' %}<a class="button button-primary" href="{{ heroButtonHref }}">{% if section.primaryLabel != nil %}<span data-p60-field="primaryLabel">{{ section.primaryLabel }}</span>{% else %}{{ heroButtonLabel }}{% endif %}</a>{% endif %}
|
|
103
107
|
{% else %}
|
|
104
|
-
{% if
|
|
108
|
+
{% if heroButtonLabel != nil and heroButtonLabel != '' and heroButtonHref != nil and heroButtonHref != '' %}<a class="button button-primary" href="{{ heroButtonHref }}">{% if section.primaryLabel != nil %}<span data-p60-field="primaryLabel">{{ section.primaryLabel }}</span>{% else %}{{ heroButtonLabel }}{% endif %}</a>{% endif %}
|
|
105
109
|
{% endif %}
|
|
106
|
-
<a class="button" href="{{ section.secondaryHref
|
|
110
|
+
{% if section.secondaryLabel != nil and section.secondaryLabel != '' and section.secondaryHref != nil and section.secondaryHref != '' %}<a class="button" href="{{ section.secondaryHref }}"><span data-p60-field="secondaryLabel">{{ section.secondaryLabel }}</span></a>{% endif %}
|
|
107
111
|
</div>
|
|
108
112
|
</div>
|
|
109
|
-
{%
|
|
113
|
+
{% if section.actionStyle != 'button' and site.actions.widget != nil and site.actions.widget != 'none' %}
|
|
110
114
|
<aside id="donate">
|
|
111
115
|
{% island 'primary_action_widget' %}
|
|
112
116
|
</aside>
|
|
113
|
-
{%
|
|
117
|
+
{% endif %}
|
|
114
118
|
</div>
|
|
115
119
|
</section>
|
|
116
120
|
{% endif %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<section class="lq-values">
|
|
2
2
|
<div class="container">
|
|
3
|
-
<h2 data-p60-field="heading">{{ section.heading
|
|
3
|
+
{% if section.heading != nil and section.heading != '' %}<h2 data-p60-field="heading">{{ section.heading }}</h2>{% endif %}
|
|
4
4
|
{% comment %}
|
|
5
5
|
data-p60-reveal-group: the engine stamps --p60-reveal-index on each child and adds
|
|
6
6
|
is-revealed when the rail scrolls into view, the stagger and motion are OURS, in theme.css,
|