@port60/template-kit 1.2.0 → 1.3.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 (52) hide show
  1. package/README.md +46 -1
  2. package/package.json +1 -1
  3. package/src/commands/create.mjs +11 -0
  4. package/src/lib/agentsMd.mjs +176 -0
  5. package/src/vendor/contract/v2/dialect.json +2 -1
  6. package/src/vendor/contract/v2/islands.json +3 -2
  7. package/src/vendor/contract/v2/manifest.schema.json +73 -2
  8. package/src/vendor/contract/v2/presentation.json +107 -0
  9. package/src/vendor/contract/v2/sections.json +120 -4
  10. package/src/vendor/contract/v2.lock.json +562 -9
  11. package/src/vendor/engine/budgets.mjs +0 -6
  12. package/src/vendor/engine/colour-roles.mjs +68 -0
  13. package/src/vendor/engine/content-footprint.mjs +0 -11
  14. package/src/vendor/engine/design-settings.mjs +34 -0
  15. package/src/vendor/engine/dialect.mjs +3 -10
  16. package/src/vendor/engine/locale.mjs +0 -5
  17. package/src/vendor/engine/majors.mjs +0 -5
  18. package/src/vendor/engine/presentation-capabilities.mjs +0 -2
  19. package/src/vendor/engine/section-fields.mjs +64 -0
  20. package/src/vendor/engine/section-heading-alignment.mjs +0 -2
  21. package/src/vendor/engine/section-presentation.mjs +127 -0
  22. package/src/vendor/validator/behaviors-runtime.js +1 -1
  23. package/src/vendor/validator/collection-link-visibility.mjs +0 -1
  24. package/src/vendor/validator/colour-treatment.mjs +154 -0
  25. package/src/vendor/validator/fixture-art.mjs +0 -15
  26. package/src/vendor/validator/focus.mjs +0 -5
  27. package/src/vendor/validator/fonts.mjs +0 -6
  28. package/src/vendor/validator/heading-alignment.mjs +0 -3
  29. package/src/vendor/validator/icons.mjs +0 -4
  30. package/src/vendor/validator/image-overlay.mjs +141 -0
  31. package/src/vendor/validator/intro-photo-framing.mjs +101 -0
  32. package/src/vendor/validator/model-reference-v2.mjs +0 -4
  33. package/src/vendor/validator/model-reference.mjs +0 -4
  34. package/src/vendor/validator/navigation-highlights.mjs +0 -3
  35. package/src/vendor/validator/platform-base.css +23 -0
  36. package/src/vendor/validator/presentation-proof.mjs +0 -2
  37. package/src/vendor/validator/preview-v1.mjs +3 -103
  38. package/src/vendor/validator/preview-v2.mjs +12 -105
  39. package/src/vendor/validator/section-fields.mjs +41 -0
  40. package/src/vendor/validator/section-layout.mjs +156 -0
  41. package/src/vendor/validator/section-presentation.mjs +225 -0
  42. package/src/vendor/validator/site-context-v2.mjs +17 -2
  43. package/src/vendor/validator/site-context.mjs +0 -14
  44. package/src/vendor/validator/validate-v1.mjs +4 -98
  45. package/src/vendor/validator/validate-v2.mjs +44 -103
  46. package/src/vendor/validator/validate.mjs +0 -2
  47. package/starter/assets/theme.css +55 -0
  48. package/starter/manifest.json +20 -0
  49. package/starter/sections/cta.liquid +2 -2
  50. package/starter/sections/hero.liquid +5 -3
  51. package/starter/sections/homeHero.liquid +6 -6
  52. package/starter/sections/values.liquid +2 -2
package/README.md CHANGED
@@ -50,11 +50,56 @@ See the [AI quickstart](https://developers.port60.com/guides/ai-quickstart/).
50
50
 
51
51
  ## The contract
52
52
 
53
- Kit 1.2.0 authors **port60-liquid@2**, content model **2.0**, using the contract under
53
+ Kit 1.3.0 authors **port60-liquid@2**, content model **2.0**, using the contract under
54
54
  `src/vendor/contract/v2`. Collection envelopes, independent header/footer navigation,
55
55
  resolved actions and page-scoped sections are explicit. V1 sources need a deliberate migration,
56
56
  not a manifest-only relabel. Historical v1 contracts remain frozen for existing platform pins.
57
57
 
58
+ ### Editor design flexibility in 1.3.0
59
+
60
+ The editor offers only controls declared by the exact selected template version. The v2
61
+ presentation registry in `src/vendor/contract/v2/presentation.json` describes the bounded
62
+ choices and reset semantics, not evidence that every template implements each choice.
63
+
64
+ - `supports.sectionPresentation` can declare per-section heading size, vertical spacing,
65
+ coordinated colour treatments, photo-only Home hero overlays and About introduction layouts.
66
+ Keep permanent hooks on the actual heading or natural section root, with token CSS scoped
67
+ to the control's permitted properties. Preserve inherited defaults, Looks, content and islands.
68
+ An absent saved key inherits; reset removes the override. Unsupported stored choices stay
69
+ stored but do not enter the rendering projection.
70
+ - About introductions can independently declare `supports.sectionFields.hero` photograph and
71
+ alt-text fields, optionally with Fill/Show whole framing. Layout choices require those fields
72
+ and are logical `image-start`, `image-end` or `stacked` compositions. No photo means the
73
+ existing no-photo design, never a borrowed Home hero image or empty column. Reset restores
74
+ the author's framing, which need not match explicit Show whole.
75
+ - The v2 `colour_roles` filter adds bounded semantic paint roles to platform-sanitised rich
76
+ text. Use `{{ section.bodyHtml | colour_roles | raw }}` consistently; it is not a sanitiser
77
+ or permission to render untrusted HTML. Colour controls cannot change geometry or content.
78
+ - Existing theme settings may use `group: "header"` or `group: "footer"` when shared layout is
79
+ supported. These remain website-wide settings, with authored choices and defaults. Add section
80
+ uses existing exact section/layout declarations, not arbitrary presets or nested blocks.
81
+ - A v2 Impact map placement requires an explicit `section.mapSlug`. Missing, cleared or
82
+ unavailable selections show no replacement map. Custom preview content must pair that slug
83
+ with the same `impactMap.slug`; historical v1 behaviour is unchanged.
84
+
85
+ The scaffold demonstrates its own supported heading, spacing, colour and About-photo/layout
86
+ choices without advertising controls on unsuitable island surfaces. Structural validation is
87
+ not computed browser proof: verify every offered choice and reset across Looks, widths, sparse
88
+ content, keyboard and no-JavaScript states before declaring support.
89
+
90
+ Deploy the compatible platform first, publish this matching kit, then validate and publish new
91
+ immutable template versions using the registry package. Existing pins do not upgrade themselves.
92
+ See the [section authoring guide](https://developers.port60.com/guides/sections-and-data/) and
93
+ generated AGENTS.md/CLAUDE.md for the detailed markup, CSS and acceptance boundaries.
94
+
95
+ ### Photo-only hero playback in 1.2.1
96
+
97
+ The compatible host supplies `.hero-playback` on the `hero_carousel` island. Photo-only
98
+ templates may hide the optional dots and reveal playback on hover or keyboard focus,
99
+ while retaining a reachable control on touch devices. The preview supplies inactive
100
+ geometry only. Campaign/content carousel controls are unchanged. Deploy the compatible
101
+ host before publishing templates that depend on this control.
102
+
58
103
  ### Optional editor controls in 1.2.0
59
104
 
60
105
  The editor only offers controls supported by the exact selected template version. Declare
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@port60/template-kit",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Build Port60 site templates locally: scaffold, live-preview, validate against the platform contract, and package for studio upload. AI-agent ready: every scaffold ships AGENTS.md and validate emits machine-readable JSON.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -75,6 +75,17 @@ Start with \`npm install\`, then:
75
75
  **Working with an AI agent?** Point it at this directory, \`AGENTS.md\` (and \`CLAUDE.md\`)
76
76
  brief it on the contract, the rules and the validate loop.
77
77
 
78
+ Optional heading sizing and section spacing are independent per section: declare only tested
79
+ choices in \`supports.sectionPresentation\`. Keep \`data-p60-heading-scale\` on the actual
80
+ heading and \`data-p60-section-spacing\` on the natural outer section root, including when
81
+ the heading is hidden. Hooks stay permanently present and empty during inheritance. Define
82
+ responsive font sizes or block-axis root padding in CSS. Never change horizontal gutters,
83
+ card gaps, heights or island internals for spacing. An absent preference inherits;
84
+ reset removes the saved choice. Do not accept arbitrary CSS or numeric sizes. The kit proves
85
+ contract structure, while browser checks must prove visible effects and safe layouts.
86
+ See https://developers.port60.com/guides/sections-and-data/#bounded-heading-size and the
87
+ machine-readable https://developers.port60.com/schemas/template-presentation-v2.json registry.
88
+
78
89
  Docs: https://developers.port60.com
79
90
  `);
80
91
 
@@ -96,11 +96,187 @@ unchanged surrounding content, reset and mobile; computed text-align alone is no
96
96
  This capability needs a coordinated host/kit release; keep existing published dependency pins
97
97
  until that supporting kit version is available.
98
98
 
99
+ ## Bounded section presentation
100
+
101
+ The presentation.json registry defines platform-owned controls, value choices, reset addresses
102
+ and targets. It is published at https://developers.port60.com/schemas/template-presentation-v2.json
103
+ and documented at /reference/presentation/. Read the exact installed kit's registry for authoring;
104
+ the editor and a future agent must also check the exact selected template version's capabilities.
105
+ Do not infer support from a similar template, the catalogue's latest release or visible markup.
106
+
107
+ hero (About introduction) supports optional imageUrl and imageAlt only with the paired exact
108
+ section declaration supports.sectionFields:{"hero":["imageUrl","imageAlt"]}. This is independent
109
+ of layout choices: fixed compositions can support photographs without sectionLayout. Guard
110
+ missing images and preserve the original no-photo design. Use data-p60-field="imageUrl" only
111
+ on the actual img; escape imageAlt into alt, never an inline text marker. No placeholder,
112
+ invented photograph or borrowed Home hero photo is allowed. Unsafe or unsupported saved image
113
+ addresses stay stored but are omitted from rendering. The host accepts bounded root-relative
114
+ or HTTP(S) addresses, never credentials, whitespace, controls or backslashes.
115
+
116
+ Optional third field photoFraming in supports.sectionFields.hero offers fill/whole for the
117
+ Introduction photo, saved at section.content.photoFraming, never presentation.photoFraming.
118
+ Absent/reset inherits the author's composition, which may crop. Explicit whole shows the
119
+ uncropped natural image: width100%, heightauto, no blanket heightcap.
120
+ Keep data-p60-photo-framing="{{ section.photoFraming }}" on the actual marked img, empty when
121
+ inherited. Exact img token rules allow only object-fit:cover and aspect-ratio:1/1,4/3,3/2,16/9,2/1
122
+ for fill, or object-fit:contain and aspect-ratio:auto for whole. An optional root tag/classes
123
+ prefix and flat media/container conditions are allowed. Never change width, copy, grid, source,
124
+ alt or wrappers via framing. Prove actual crop vs natural whole in a browser with landscape,
125
+ portrait, square and panorama inputs; verify selector specificity does not mask the control.
126
+ The editor patches the existing image, then reconciles the section without refetching the photo
127
+ or navigating. Home hero framing and its default remain independent and unchanged.
128
+
129
+ sectionLayout is a per-section capability with image-start, image-end and stacked choices.
130
+ It is not a template-wide switch. Its hero target requires the independent photograph pair
131
+ above. The scaffold declares both; check the exact installed kit before authoring and do not
132
+ infer support from the registry alone. The layout proof preserves the natural root, unchanged
133
+ DOM order and separate frame/media/content roles.
134
+ Token CSS must be gated by data-p60-layout-has-media and may change only bounded grid tracks
135
+ and media/content grid-row/grid-column. No default token rule, arbitrary descendants, extra
136
+ containers, spacing, sizing, paint or platform islands. Start/end follow site direction.
137
+ Author opt-in needs exact default/reset equality, no-image safety and narrow single-column
138
+ browser evidence across every Look, plus long/cleared copy and repeated-section isolation.
139
+
140
+ headingScale and sectionSpacing are independent optional sectionPresentation controls. For example,
141
+ supports.sectionPresentation:{"homeHero":{"headingScale":{"options":["compact","standard","large"],"default":"standard"},"sectionSpacing":{"options":["compact","standard","spacious"],"default":"standard"}}}.
142
+ Every section key must appear in supports.sections. Declare only the two or three unique tokens
143
+ you have implemented, from compact/standard/large for headingScale, or compact/standard/spacious
144
+ for sectionSpacing; default must be one of those tokens. At least one control is required per
145
+ declared section; a spacing-only section does not need a heading. No raw CSS,
146
+ arbitrary numeric sizes, selectors or conditional expressions belong in this declaration.
147
+ The author defines a safe responsive size for each token in theme.css, not the editor or host.
148
+
149
+ imageOverlay is optional for homeHero only and requires supports.heroImagery:true. Declare
150
+ two or three choices from subtle/standard/strong and the inherited default. Save it at
151
+ section.content.presentation.imageOverlay; Liquid reads section.presentation.imageOverlay.
152
+ Keep data-p60-image-overlay on the existing first outer hero root, empty when inherited.
153
+ Mark photo-only wrappers data-p60-image-overlay-media and empty decorative overlay elements
154
+ data-p60-image-overlay-layer. Token CSS may change only palette-derived background,
155
+ background-image or background-color on marked layers, marked media ::before/::after, or
156
+ the documented .hero-slide-scrim beneath marked media. Never affect image opacity, filters,
157
+ geometry, copy, widgets or required text-readability scrims. Prove every static and carousel
158
+ branch, Fill and Whole, every Look, zero/one/two/max photos and no JavaScript. Default and reset
159
+ must match the original treatment; unsupported choices stay stored but do not render.
160
+ The inherited default can omit a CSS override; other choices need real matching layer rules.
161
+ Do not opt in a template whose branches cannot honour this. Starter deliberately remains
162
+ unsupported. Read /guides/sections-and-data/#bounded-photo-overlays for the full proof boundary.
163
+
164
+ colourTreatment is optional on non-island sections, excluding homeHero and impactMap. Declare
165
+ two or three unique standard/soft/contrast choices and an inherited default in sectionPresentation.
166
+ Persist only section.content.presentation.colourTreatment. Keep data-p60-colour-treatment on the
167
+ single natural outer root, empty when inherited. Mark authored descendants with
168
+ data-p60-colour-role="text|muted|accent|surface|border|link|button|secondary-button" (one role).
169
+ Nondefault token CSS must pair root background-color and color, cover every rendered role and
170
+ provide hover/focus-visible paint for link and button roles. Read existing Look palette variables;
171
+ never redefine global or section custom properties. Only color, background-color, border-color,
172
+ outline-color and text-decoration-color declarations are allowed; never use !important.
173
+ Do not alter geometry, images,
174
+ opacity, filters or islands. Never add padding/wrappers merely to make a colour control work.
175
+ Default-token CSS is forbidden so inheritance/default/reset keep the exact original design.
176
+ Use only flat natural root tag/classes + exact token, optionally followed by an owned role selector
177
+ and hover/focus-visible/active. No nested selectors or at-rules inside a token block.
178
+ Prove visible coordinated changes and contrast in every Look,
179
+ width, interaction and no-JavaScript state; structural CSS checks alone cannot prove that.
180
+ Read /guides/sections-and-data/#coordinated-section-colours for the full author contract.
181
+
182
+ For a colour-capable About introduction, render section.bodyHtml | colour_roles | raw on every
183
+ render, even inherited/default/reset. The v2 colour_roles filter decorates already-sanitised HTML:
184
+ h2/h3/h4=text, p/ul/ol/li=muted, a=link, blockquote=surface, hr=border. Cover every rendered role
185
+ and link hover/focus-visible. Inline emphasis inherits; images and islands are never decorated.
186
+ The filter only adds fixed attributes, preserving text, hrefs, markup and text-align. It is not
187
+ a sanitiser, never makes arbitrary HTML safe and does not bypass output escaping; raw stays explicit.
188
+ Do not conditionally replace or rewrap the body when changing a colour choice.
189
+
190
+ The saved address is section.content.presentation.headingScale. Inside Liquid the section variable
191
+ is already that content, so read section.presentation.headingScale. An absent key inherits your
192
+ existing CSS; reset removes it. Do not write a default into content or use a Liquid default filter.
193
+ An explicit choice equal to the default remains explicit. Unsupported saved choices stay stored
194
+ but are omitted from the render projection; switching templates must never erase them.
195
+
196
+ Keep data-p60-heading-scale="{{ section.presentation.headingScale }}" permanently on each actual
197
+ visible h1 to h6 section heading, including inherited headings without a field marker. The default
198
+ hook is empty, not absent. Hidden headings may omit both the heading and hook. Never put this
199
+ hook on a heading group, eyebrow, introduction, card title, action or platform island.
200
+ Write token-specific font-size rules scoped to this hook and the relevant heading family.
201
+ The declared default must have the same computed size as inheritance at each supported width
202
+ and Look. Do not change copy, heading levels or unrelated markup in response to the token.
203
+
204
+ For spacing, save section.content.presentation.sectionSpacing and read section.presentation.sectionSpacing
205
+ in Liquid. Keep data-p60-section-spacing="{{ section.presentation.sectionSpacing }}" permanently on
206
+ the natural outer section root, with an empty inherited hook, even when its heading is hidden.
207
+ Never add a wrapper or put the hook on an inner container, heading, card or platform island.
208
+ Token-specific CSS must target only that root and change only padding-block, padding-block-start,
209
+ padding-block-end, padding-top or padding-bottom. Never use padding shorthand or change horizontal
210
+ gutters, gaps, height, typography, descendants, pseudo-elements or island internals. Keep asymmetric
211
+ top/bottom authored padding intact in the default. The declared default must compute identically
212
+ to inheritance across widths and Looks. Omit spacing when the existing root has no suitable padding.
213
+ Multiple outer roots, including ancillary behaviour siblings, are unsupported; preserve them and
214
+ omit spacing rather than adding a wrapper. If clearing copy already omits the entire section,
215
+ all spacing choices must preserve that omission, never create a section in response to the token.
216
+
217
+ Kit preview, live preview and final rendering use the same accepted-value projection. The kit
218
+ checks declarations, heading/root hooks, structural isolation and token-specific CSS declarations;
219
+ it cannot prove computed CSS or visual quality. Check every option and reset in a browser across
220
+ Looks, narrow/wide viewports, long/empty/inherited headings, repeated sections and no JavaScript.
221
+ Prove visible size and spacing changes without overflow or changing unrelated layout. Respect reduced motion.
222
+ Existing headingAlignment and showCollectionLink addresses are unchanged. This is a bounded
223
+ presentation registry, not an arbitrary schema-driven editor or condition language.
224
+ Ship the compatible host and kit before publishing newly versioned template declarations.
225
+
226
+ The kit scaffold implements both controls on homeHero, hero, values and cta using its existing
227
+ heading families and natural root padding. people, campaigns and impactMap remain excluded:
228
+ their current utility or island roots have no suitable authored spacing surface. Preserve
229
+ the whole-photo hero's zero top padding and the form variant's distinct bottom padding.
230
+
99
231
  Declare supports.fieldMarkers:true when showing authored field markers. A marker such as
100
232
  data-p60-field="title" or data-p60-field="items.{{ forloop.index0 }}.label" addresses only that
101
233
  section's content. Its node must contain exactly the authored value. Use a span when punctuation
102
234
  or generated text surrounds it. Never mark source records, generated labels or resolved actions.
103
235
 
236
+ ## Shared inspectors and section starting arrangements
237
+
238
+ An implemented v2 settings.schema entry may use group:"header" or group:"footer" with
239
+ supports.layout:true to appear in the matching shared website inspector. Historical v1 groups
240
+ and artifacts remain unchanged. This is explicit exact-artifact metadata, not a guessed
241
+ meaning of a key or template name. Existing color/select/toggle/font kinds, saved theme keys,
242
+ body data-p60s-<key> attributes and --p60s-<key> variables are unchanged. Implement every option
243
+ in the template's own CSS/markup; grouping alone does not create or prove a visual effect.
244
+ Author bounded artwork sizes per design, not universal ranges; leave unsupported placements
245
+ and choices absent.
246
+ Logo sizing affects artwork, not unrelated type. Keep useful no-logo and empty-social states,
247
+ menu destinations, identity, required legal links, consent and transactions. Verify every Look,
248
+ responsive width, keyboard and no-JavaScript state, plus exact inherited/default/reset equality.
249
+
250
+ Header/Footer controls share the existing theme working copy. Reset removes only the named
251
+ override; Design bulk reset preserves shared groups and unsupported saved choices. Explicit
252
+ defaults/false/blanks remain resettable. Looks still apply only their declared keys once.
253
+ Brand and menu controls retain their existing source records and website draft/publication flow.
254
+ Social addresses remain in Website settings with their own permissions and save lifecycle;
255
+ presentation choices never create or delete addresses or automatically save their source.
256
+
257
+ The visual Add section chooser uses exact supports.sections, allowed page types, compositions
258
+ and the existing per-section sectionLayout declarations. Its diagrams are schematic guides,
259
+ never screenshots or tenant content. There is no arbitrary presets schema or nested-container
260
+ model. Add inserts one new stable-key section with empty content or only the chosen layout
261
+ preference. Cancel has no effect; existing sections stay untouched. Template default omits the
262
+ override. Do not seed sample text, images, statistics, action links or source records. A layout
263
+ choice does not add a photograph, create an empty media column or change mobile ownership.
264
+
265
+ V2 impactMap requires explicit section.mapSlug. Missing/null/blank means no selected map;
266
+ never substitute the first public map. Unavailable, unpublished or archived selected maps have
267
+ no replacement fallback. Historical v1/built-in default-map behaviour is unchanged. The v2 kit
268
+ preview only shows its non-interactive map fixture when the selected slug matches that fixture:
269
+ canonical sample mapSlug:"our-work" matches impactMap.slug:"our-work"; minimal stays empty.
270
+ Custom demos must also explicitly select their matching fixture, never fetch tenant records.
271
+
272
+ Connected source choices are bounded typed references, not duplicate content or free expressions.
273
+ Map title/description/points stay in Maps; local introduction copy is only for templates that
274
+ actually render it. Cause buttons retain causeId and the public address copied when chosen,
275
+ not a live link expression following future slug changes. Missing sources keep saved choices
276
+ and show a notice; never choose replacements or create source records to fill a layout.
277
+ Use the compatible platform and kit before releasing newly versioned declarations. Source
278
+ tests, browser acceptance, local deployment and remote release are separate gates.
279
+
104
280
  ## Navigation and actions
105
281
 
106
282
  Optional supports.navigationModes is {options:["simple","mega"],default:"mega"} (either supported
@@ -90,7 +90,8 @@
90
90
  "url_encode",
91
91
  "where",
92
92
  "t",
93
- "local_date"
93
+ "local_date",
94
+ "colour_roles"
94
95
  ],
95
96
  "excluded": {
96
97
  "tags": [
@@ -106,7 +106,7 @@
106
106
  "name": "hero_carousel",
107
107
  "label": "Hero image carousel",
108
108
  "status": "available",
109
- "description": "Rotation for the home hero's photographs (homeHero.images, 2+): auto-advance every ~6s, pauses on hover/focus, honours prefers-reduced-motion (no auto-advance), swipe on touch, dot navigation. The platform owns the motion; the TEMPLATE owns the look, including the tonal treatment: every slide ships an empty .hero-slide-scrim layer over its image, and the template colours it from its OWN palette variables (a colour wash, a blend, a gradient) so any uploaded photo sits inside the template's tones. Renders nothing with fewer than 2 images, single-image and no-image states are the template's own renderer, not this island's.",
109
+ "description": "Photo-only rotation for homeHero.images (2+): advances every ~6s, pauses on hover and offers persistent Pause/Resume. Keyboard navigation or focus outside that button stops rotation until explicitly resumed. Reduced motion prevents autoplay, including after a live preference change. Supports horizontal touch swipe and direction-aware arrow keys, Home/End, plus optional dot navigation. Templates may hide dots when the .hero-playback control exists. They may reveal playback on photograph hover and keyboard focus, but must retain keyboard reachability, a visible focus indicator and a control that is available without hover on touch devices. This is separate from campaign/content carousels. The platform owns motion; the template styles the frame and the empty .hero-slide-scrim tonal layer using its palette. Fewer than 2 images render nothing: the template owns single/no-image states.",
110
110
  "params": [],
111
111
  "stylingApi": [
112
112
  ".hero-carousel",
@@ -117,7 +117,8 @@
117
117
  ".hero-slide-scrim",
118
118
  ".hero-dots",
119
119
  ".hero-dot",
120
- ".hero-dot--active"
120
+ ".hero-dot--active",
121
+ ".hero-playback"
121
122
  ]
122
123
  },
123
124
  {
@@ -192,6 +192,75 @@
192
192
  "description": "True when the sections mark which node shows which field with data-p60-field, so the editor can put the caret on the page instead of in a side panel. The address is inside that section's own content: data-p60-field=\"title\", or data-p60-field=\"items.{{ forloop.index0 }}.label\" for an entry in a list. Purely additive: the attribute is markup, the contract is untouched, and a template that marks nothing keeps the section-level editor. The validator reads the marks from the source and warns on any that does not name a field of that section. Missing or false leaves this exact template version editing in the panel.",
193
193
  "$comment": "Follows the navigationHighlights pattern: declared honesty, proven by the validator."
194
194
  },
195
+ "sectionFields": {
196
+ "type": "object",
197
+ "additionalProperties": false,
198
+ "properties": {
199
+ "hero": { "type": "array", "minItems": 2, "maxItems": 3, "uniqueItems": true, "items": { "enum": ["imageUrl", "imageAlt", "photoFraming"] }, "allOf": [{ "contains": { "const": "imageUrl" } }, { "contains": { "const": "imageAlt" } }] }
200
+ },
201
+ "description": "Per-section opt-in to optional authored fields in this exact template version. The initial capability is the About introduction photograph: hero must also occur in supports.sections, and imageUrl/imageAlt are declared together. Optional photoFraming adds fill/whole on that photo; absent/reset inherits the author's composition while explicit whole shows the uncropped natural ratio. A fixed-layout photograph can be supported without sectionLayout. Missing support keeps saved photo fields but omits them from rendering and offers no new image picker. Validation proves supplied source/alt, no-photo omission, bounded framing and section ownership."
202
+ },
203
+ "sectionPresentation": {
204
+ "type": "object",
205
+ "maxProperties": 16,
206
+ "propertyNames": { "enum": ["hero", "values", "cta", "homeHero", "impact", "events", "articles", "campaigns", "emergency", "stats", "appealGrid", "services", "documents", "people", "impactMap", "courses"] },
207
+ "additionalProperties": {
208
+ "type": "object",
209
+ "additionalProperties": false,
210
+ "minProperties": 1,
211
+ "properties": {
212
+ "sectionLayout": {
213
+ "type": "object",
214
+ "additionalProperties": false,
215
+ "required": ["options", "default"],
216
+ "properties": {
217
+ "options": { "type": "array", "minItems": 2, "maxItems": 3, "uniqueItems": true, "items": { "enum": ["image-start", "image-end", "stacked"] } },
218
+ "default": { "enum": ["image-start", "image-end", "stacked"] }
219
+ },
220
+ "description": "Per-section author opt-in for an About introduction with optional authored imagery. Start/end follow site direction. Permanent data-p60-section-layout on the single natural root, with data-p60-layout-has-media only when the photo exists and explicit frame/media/content roles. Token CSS changes only bounded grid tracks and slot placement, never copy, DOM order, imagery, gutters, typography or platform islands. Default/reset keeps inherited styling. Every narrow-screen choice retains authored single-column stacking."
221
+ },
222
+ "colourTreatment": {
223
+ "type": "object",
224
+ "additionalProperties": false,
225
+ "required": ["options", "default"],
226
+ "properties": {
227
+ "options": { "type": "array", "minItems": 2, "maxItems": 3, "uniqueItems": true, "items": { "enum": ["standard", "soft", "contrast"] } },
228
+ "default": { "enum": ["standard", "soft", "contrast"] }
229
+ },
230
+ "description": "Coordinated section paint within the selected Look. Requires a permanent data-p60-colour-treatment on the single natural section root and bounded data-p60-colour-role descendants. Nondefault treatments coordinate root background/text and every marked role, including link/button hover and focus-visible. No shared palette rebinding, images, layout or platform islands. Home hero and impact maps are unsupported. Missing/reset preserves inherited styling."
231
+ },
232
+ "imageOverlay": {
233
+ "type": "object",
234
+ "additionalProperties": false,
235
+ "required": ["options", "default"],
236
+ "properties": {
237
+ "options": { "type": "array", "minItems": 2, "maxItems": 3, "uniqueItems": true, "items": { "enum": ["subtle", "standard", "strong"] } },
238
+ "default": { "enum": ["subtle", "standard", "strong"] }
239
+ },
240
+ "description": "Home hero only. Safe palette-derived decorative photo treatment choices, independently proven for static and carousel imagery, Fill and Whole. Preserve required text-readability scrims. Missing/reset preserves inherited treatment; no source editing, image opacity or geometry changes."
241
+ },
242
+ "sectionSpacing": {
243
+ "type": "object",
244
+ "additionalProperties": false,
245
+ "required": ["options", "default"],
246
+ "properties": {
247
+ "options": { "type": "array", "minItems": 2, "maxItems": 3, "uniqueItems": true, "items": { "enum": ["compact", "standard", "spacious"] } },
248
+ "default": { "enum": ["compact", "standard", "spacious"] }
249
+ }
250
+ },
251
+ "headingScale": {
252
+ "type": "object",
253
+ "additionalProperties": false,
254
+ "required": ["options", "default"],
255
+ "properties": {
256
+ "options": { "type": "array", "minItems": 2, "maxItems": 3, "uniqueItems": true, "items": { "enum": ["compact", "standard", "large"] } },
257
+ "default": { "enum": ["compact", "standard", "large"] }
258
+ }
259
+ }
260
+ }
261
+ },
262
+ "description": "Exact-version opt-in to independent platform-owned section presentation controls. Every key must be in supports.sections and declare at least one known control. Each control's default must occur in its options and describe inherited responsive styling. Saved explicit choices are projected into section.presentation; absence inherits without writing defaults. headingScale requires a permanent data-p60-heading-scale hook on the actual heading and authored font-size CSS. sectionSpacing requires a permanent data-p60-section-spacing hook on the natural outer section root and authored block-axis padding CSS, even without a heading; never alter horizontal gutters, card gaps, heights or island internals. Missing declarations are unsupported. No arbitrary values, conditions or CSS selectors are accepted."
263
+ },
195
264
  "sectionHeadingAlignment": {
196
265
  "type": "object",
197
266
  "maxProperties": 16,
@@ -413,9 +482,11 @@
413
482
  "type": "string",
414
483
  "enum": [
415
484
  "typography",
416
- "looks"
485
+ "looks",
486
+ "header",
487
+ "footer"
417
488
  ],
418
- "description": "Where the admin surfaces this knob: 'typography' → the Typography section beside the font slots; 'looks' → the Looks section with the one-click bundles (colour schemes and accent dials belong there, and ungrouped color knobs plus a knob keyed 'scheme' default there anyway); other ungrouped knobs render under Template options."
489
+ "description": "Where the admin surfaces this knob: typography, looks, or the shared header/footer inspector. Header and footer groups explicitly declare template-authored global chrome settings, not a new content source. Every option must have template-owned CSS or markup; omitted groups never imply support. Other ungrouped knobs render under Template options."
419
490
  },
420
491
  "weights": {
421
492
  "type": "array",
@@ -0,0 +1,107 @@
1
+ {
2
+ "version": 1,
3
+ "description": "Platform-owned presentation meanings. Templates declare supported choices; saved values remain tenant-owned. An absent override inherits the exact artifact's authored default. Hidden or unsupported values remain stored but are not projected into template rendering.",
4
+ "sectionTypes": ["hero", "values", "cta", "homeHero", "impact", "events", "articles", "campaigns", "emergency", "stats", "appealGrid", "services", "documents", "people", "impactMap", "courses"],
5
+ "legacySectionAliases": {"programmes": "services", "resources": "documents", "whatsOn": "events"},
6
+ "controls": {
7
+ "sectionLayout": {
8
+ "label": "Section layout",
9
+ "kind": "choice",
10
+ "scope": "section",
11
+ "sectionTypes": ["hero"],
12
+ "contentFields": {"imageUrl": "image", "imageAlt": "text"},
13
+ "address": ["presentation", "sectionLayout"],
14
+ "options": ["image-start", "image-end", "stacked"],
15
+ "labels": {"image-start": "Image at start", "image-end": "Image at end", "stacked": "Image above"},
16
+ "minimumOptions": 2,
17
+ "reset": "remove",
18
+ "target": "section-layout",
19
+ "responsive": "author-defined",
20
+ "attribute": "data-p60-section-layout",
21
+ "description": "Author-owned image/text layout for an explicitly supported About introduction. Start/end follow the site direction. Mark the single natural outer root and existing frame/media/content slots; token CSS changes only bounded grid tracks and slot placement when a photograph exists. Missing/reset preserves authored styling. Preserve copy, imagery, reading order, links, gutters and narrow-screen stacking; never invent absent media, add arbitrary containers or target platform islands. Every exact section declaration is independent; missing support is unsupported."
22
+ },
23
+ "colourTreatment": {
24
+ "label": "Colour treatment",
25
+ "kind": "choice",
26
+ "scope": "section",
27
+ "sectionTypes": ["hero", "values", "cta", "impact", "events", "articles", "campaigns", "emergency", "stats", "appealGrid", "services", "documents", "people", "courses"],
28
+ "address": ["presentation", "colourTreatment"],
29
+ "options": ["standard", "soft", "contrast"],
30
+ "labels": {"standard": "Standard", "soft": "Soft", "contrast": "Contrast"},
31
+ "minimumOptions": 2,
32
+ "reset": "remove",
33
+ "target": "section-colour-treatment",
34
+ "responsive": "author-defined",
35
+ "attribute": "data-p60-colour-treatment",
36
+ "description": "Coordinated author-owned section surface, copy, links and buttons within the selected Look. Missing/reset retains the original treatment. Mark the natural outer root and explicitly owned colour roles; token CSS changes paint only, never shared palette variables, imagery, layout or island internals. Home hero, impact maps and sections containing platform islands are not supported in this slice."
37
+ },
38
+ "headingScale": {
39
+ "label": "Heading size",
40
+ "kind": "choice",
41
+ "scope": "section",
42
+ "address": ["presentation", "headingScale"],
43
+ "options": ["compact", "standard", "large"],
44
+ "labels": {"compact": "Compact", "standard": "Standard", "large": "Large"},
45
+ "minimumOptions": 2,
46
+ "reset": "remove",
47
+ "target": "section-heading",
48
+ "responsive": "author-defined",
49
+ "attribute": "data-p60-heading-scale",
50
+ "description": "Changes only the actual section heading's responsive font size, never its heading level, eyebrow, body, cards, buttons or islands. Templates provide each size in their own CSS. The declared default describes inherited styling; it is not written into tenant content."
51
+ },
52
+ "sectionSpacing": {
53
+ "label": "Section spacing",
54
+ "kind": "choice",
55
+ "scope": "section",
56
+ "address": ["presentation", "sectionSpacing"],
57
+ "options": ["compact", "standard", "spacious"],
58
+ "labels": {"compact": "Compact", "standard": "Standard", "spacious": "Spacious"},
59
+ "minimumOptions": 2,
60
+ "reset": "remove",
61
+ "target": "section-root",
62
+ "responsive": "author-defined",
63
+ "attribute": "data-p60-section-spacing",
64
+ "description": "Changes only the natural outer section root's block-axis padding, never horizontal gutters, card gaps, heights or island internals. Templates provide each spacing in their own CSS. The declared default describes inherited styling; it is not written into tenant content. Keep the permanent hook even when the heading is hidden."
65
+ },
66
+ "imageOverlay": {
67
+ "label": "Photo overlay",
68
+ "kind": "choice",
69
+ "scope": "section",
70
+ "sectionTypes": ["homeHero"],
71
+ "requiresSupport": "heroImagery",
72
+ "address": ["presentation", "imageOverlay"],
73
+ "options": ["subtle", "standard", "strong"],
74
+ "labels": {"subtle": "Subtle", "standard": "Standard", "strong": "Strong"},
75
+ "minimumOptions": 2,
76
+ "reset": "remove",
77
+ "target": "section-image-treatment",
78
+ "responsive": "author-defined",
79
+ "attribute": "data-p60-image-overlay",
80
+ "description": "Changes only authored palette-derived decorative photo treatments in Home hero, never image opacity, source media, geometry, copy, widgets or a required readability scrim. Each exact artifact declares its safe choices and inherited default. Mark the existing photo wrapper with data-p60-image-overlay-media and empty decorative layers with data-p60-image-overlay-layer; token CSS may target those layers, marked wrapper pseudo-elements or the documented hero-slide-scrim. Missing and reset values preserve the authored treatment."
81
+ },
82
+ "headingAlignment": {
83
+ "label": "Desktop heading alignment",
84
+ "kind": "choice",
85
+ "scope": "section",
86
+ "address": ["headingAlignment"],
87
+ "options": ["start", "center", "end"],
88
+ "minimumOptions": 1,
89
+ "reset": "remove",
90
+ "target": "section-heading-and-eyebrow",
91
+ "responsive": "desktop-1024",
92
+ "attribute": "data-p60-heading-align",
93
+ "declaration": "sectionHeadingAlignment"
94
+ },
95
+ "showCollectionLink": {
96
+ "label": "Show collection link",
97
+ "kind": "toggle",
98
+ "scope": "section",
99
+ "address": ["showCollectionLink"],
100
+ "reset": "remove",
101
+ "target": "collection-link",
102
+ "responsive": "all",
103
+ "attribute": "data-p60-collection-link",
104
+ "declaration": "sectionCollectionLinkVisibility"
105
+ }
106
+ }
107
+ }