@taprootio/site-authoring 0.8.3 → 0.8.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taprootio/site-authoring",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Taproot site authoring CLI: sign in once in the browser, then pull, validate, push, preview, approve, and deploy a site from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@taprootio/espalier": "4.
|
|
31
|
+
"@taprootio/espalier": "4.14.1"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"test": "node --test",
|
package/src/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const CLI_NAME = "@taprootio/site-authoring";
|
|
2
|
-
export const CLI_VERSION = "0.8.
|
|
2
|
+
export const CLI_VERSION = "0.8.4";
|
|
3
3
|
export const CLI_BINARY_NAME = "taproot-site";
|
|
4
4
|
export const RESULT_SCHEMA_VERSION = 1;
|
|
5
5
|
export const CONFIG_FILE_NAME = "taproot-site.json";
|
|
@@ -252,6 +252,8 @@ const IMAGE_BANNER = Object.freeze({
|
|
|
252
252
|
// the site header; `viewport` fills exactly that space (TR00413).
|
|
253
253
|
heightMode: enumOf("ratio", "viewport"),
|
|
254
254
|
contentPosition: enumOf("bottom-start", "bottom", "bottom-end", "center", "top-start", "top", "top-end"),
|
|
255
|
+
contentGutter: enumOf("frame", "page"),
|
|
256
|
+
followingSpacing: enumOf("standard", "compact"),
|
|
255
257
|
scrim: enumOf("auto", "none", "flat", "top", "bottom", "left", "right", "radial"),
|
|
256
258
|
scrimStrength: enumOf("soft", "medium", "strong"),
|
|
257
259
|
bannerScheme: enumOf("auto", "light", "dark"),
|
|
@@ -260,7 +262,7 @@ const IMAGE_BANNER = Object.freeze({
|
|
|
260
262
|
// A built-in or application-registered Espalier texture name.
|
|
261
263
|
texture: textureName(),
|
|
262
264
|
textureScale: enumOf("fine", "medium", "coarse"),
|
|
263
|
-
imageMotion: enumOf("none", "slow-zoom", "drift-start", "drift-end"),
|
|
265
|
+
imageMotion: enumOf("none", "slow-zoom", "drift-start", "drift-end", "parallax-subtle", "parallax"),
|
|
264
266
|
});
|
|
265
267
|
|
|
266
268
|
const SPACER = Object.freeze({
|
|
@@ -371,6 +373,8 @@ const COMPONENT_DEFINITIONS = Object.freeze({
|
|
|
371
373
|
"Identify the speaker with authorName; use authorTitle when it gives useful context.",
|
|
372
374
|
"Author images need descriptive alt text unless they are purely decorative.",
|
|
373
375
|
"Keep carousel intervals long enough for the full quotation to be read.",
|
|
376
|
+
"Set carousel:true for directional previous/next arrows, dot navigation and a play/pause control. Navigation or keyboard focus pauses rotation; reduced-motion users get manual navigation without sliding or automatic rotation.",
|
|
377
|
+
"Use real attributed reviews, or clearly label fictional demo reviews. Never imply invented testimonials are genuine customer endorsements.",
|
|
374
378
|
],
|
|
375
379
|
{
|
|
376
380
|
items: [{
|
|
@@ -494,6 +498,8 @@ const COMPONENT_DEFINITIONS = Object.freeze({
|
|
|
494
498
|
compactRatio: "3/2",
|
|
495
499
|
heightMode: "ratio",
|
|
496
500
|
contentPosition: "center",
|
|
501
|
+
contentGutter: "frame",
|
|
502
|
+
followingSpacing: "standard",
|
|
497
503
|
scrim: "auto",
|
|
498
504
|
scrimStrength: "medium",
|
|
499
505
|
bannerScheme: "auto",
|
|
@@ -505,6 +511,16 @@ const COMPONENT_DEFINITIONS = Object.freeze({
|
|
|
505
511
|
[
|
|
506
512
|
"Set altText to describe meaningful banner imagery; leave it empty only when the image is decorative.",
|
|
507
513
|
"Choose a scrim and scheme that keep overlay text readable across the image.",
|
|
514
|
+
"For a full-bleed photograph with text aligned to the surrounding page well, set contentGutter to page. followingSpacing: compact reduces the top padding of an immediately following section so a bottom banner heading reads with its supporting paragraph. Use a banner-only contentPadding: none section followed by a standard text section; keep their theme contexts consistent. Defaults preserve frame-edge text and standard section spacing.",
|
|
515
|
+
"Compose around the actual photograph: identify the meaningful subject and a quiet area for copy. Do not cover faces, product details, or high-detail edges. A focal point controls cropping, not text placement; inspect wide and compact crops separately. Keep the subject's face, ears, and other identifying details inside the compact frame, including at both ends of parallax travel. Adjust focus toward the subject rather than reusing a generic center; reduce motion or choose a roomier crop if preserving the subject requires it.",
|
|
516
|
+
"Use contentPosition to place copy in that quiet area, not automatically in the center. Keep one dominant message and nearby supporting copy/actions; if the photo has no safe text area, use a split hero or a separate text section instead of forcing an overlay.",
|
|
517
|
+
"Use the rule of thirds as a composition guide: consider the subject near one third and a compact text group in quiet space on the opposite side. Golden-ratio proportions are optional alternatives, not a proven engagement formula. Neither guide overrides legibility, the actual subject, or the compact crop. Current contentPosition values are edge/center anchors, not exact third or golden-ratio coordinates; do not invent unsupported positioning properties.",
|
|
518
|
+
"Check contrast behind the actual letters at both motion endpoints and both themes: WCAG AA requires 4.5:1 for ordinary text and 3:1 for qualifying large text. A text shadow alone is not a contrast guarantee. Keep text and actions stationary; recheck at mobile widths and enlarged text.",
|
|
519
|
+
"Prefer parallax-subtle or parallax when motion is useful: motion follows the reader's scrolling and stops when they stop. Infinite ambient loops are an explicit art-direction choice, not the default recommendation.",
|
|
520
|
+
"imageMotion is optional and defaults to none. slow-zoom is a cinematic 18% zoom over 6 seconds, alternating; drift-start and drift-end travel horizontally over 8 seconds with a 14% safety crop.",
|
|
521
|
+
"These retuned ambient presets affect existing published banners when their shared runtime updates, without a per-site republish. Preview existing sites before rolling out the runtime.",
|
|
522
|
+
"parallax-subtle and parallax are scroll-linked image motion, not autoplay: scroll to see them. Published pages prefer CSS view timelines and use a platform-owned JavaScript fallback in unsupported browsers, including Firefox; authors need no custom code. Both paths respect reduced motion. They travel vertically by +/-4% and +/-8% of image height with 12% and 22% overscan. Keep focal subjects away from edges; overlay text remains stationary in its frame.",
|
|
523
|
+
"All image motion is disabled for prefers-reduced-motion. Without native view timelines and JavaScript, parallax remains static. Choose one motion per banner; never put essential text in the raster image.",
|
|
508
524
|
],
|
|
509
525
|
{
|
|
510
526
|
image: null,
|
|
@@ -516,7 +532,7 @@ const COMPONENT_DEFINITIONS = Object.freeze({
|
|
|
516
532
|
scrim: "bottom",
|
|
517
533
|
scrimStrength: "medium",
|
|
518
534
|
texture: "paper",
|
|
519
|
-
imageMotion: "
|
|
535
|
+
imageMotion: "parallax-subtle",
|
|
520
536
|
},
|
|
521
537
|
),
|
|
522
538
|
});
|
|
@@ -183,16 +183,19 @@ function renderFreeFormRootNodes(
|
|
|
183
183
|
): string {
|
|
184
184
|
const rendered: string[] = [];
|
|
185
185
|
let flow: ProseMirrorNode[] = [];
|
|
186
|
+
let compactFollowing = false;
|
|
186
187
|
const flushFlow = () => {
|
|
187
188
|
if (flow.length === 0) return;
|
|
188
|
-
rendered.push(renderSection(flow, {}, "implicit", options, "root"));
|
|
189
|
+
rendered.push(renderSection(flow, {}, "implicit", options, "root", compactFollowing));
|
|
190
|
+
compactFollowing = false;
|
|
189
191
|
flow = [];
|
|
190
192
|
};
|
|
191
193
|
|
|
192
194
|
for (const node of nodes) {
|
|
193
195
|
if (node.type === freeFormSectionRegistry.section.nodeType || isRootBandComponent(node)) {
|
|
194
196
|
flushFlow();
|
|
195
|
-
rendered.push(renderNode(node, options, "root"));
|
|
197
|
+
rendered.push(renderNode(node, options, "root", compactFollowing));
|
|
198
|
+
compactFollowing = joinsFollowingSection(node);
|
|
196
199
|
continue;
|
|
197
200
|
}
|
|
198
201
|
flow.push(node);
|
|
@@ -201,6 +204,20 @@ function renderFreeFormRootNodes(
|
|
|
201
204
|
return rendered.join("");
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
function joinsFollowingSection(node: ProseMirrorNode): boolean {
|
|
208
|
+
if (node.type === "section") {
|
|
209
|
+
return node.attrs?.contentPadding === "none" && node.content?.length === 1
|
|
210
|
+
&& joinsFollowingSection(node.content[0]);
|
|
211
|
+
}
|
|
212
|
+
if (node.type !== "componentBlock" || node.attrs?.componentType !== "image-banner") return false;
|
|
213
|
+
try {
|
|
214
|
+
const data = JSON.parse(stringAttr(node.attrs?.componentData) ?? "{}");
|
|
215
|
+
return data?.followingSpacing === "compact";
|
|
216
|
+
} catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
204
221
|
function isRootBandComponent(node: ProseMirrorNode): boolean {
|
|
205
222
|
if (node.type !== "componentBlock") return false;
|
|
206
223
|
const componentType = stringAttr(node.attrs?.componentType);
|
|
@@ -215,6 +232,7 @@ function renderNode(
|
|
|
215
232
|
node: ProseMirrorNode,
|
|
216
233
|
options: RenderProseMirrorOptions,
|
|
217
234
|
placement: RenderPlacement,
|
|
235
|
+
compactFollowing = false,
|
|
218
236
|
): string {
|
|
219
237
|
switch (node.type) {
|
|
220
238
|
case "doc":
|
|
@@ -272,6 +290,7 @@ function renderNode(
|
|
|
272
290
|
"explicit",
|
|
273
291
|
options,
|
|
274
292
|
placement === "root" ? "section" : "nested",
|
|
293
|
+
compactFollowing,
|
|
275
294
|
);
|
|
276
295
|
case "rawHtml": {
|
|
277
296
|
const html = stringAttr(node.attrs?.html) ?? "";
|
|
@@ -685,6 +704,7 @@ function renderSection(
|
|
|
685
704
|
kind: "explicit" | "implicit",
|
|
686
705
|
options: RenderProseMirrorOptions,
|
|
687
706
|
contentPlacement: RenderPlacement,
|
|
707
|
+
compactFollowing = false,
|
|
688
708
|
): string {
|
|
689
709
|
const definitions = freeFormSectionRegistry.section.attrs;
|
|
690
710
|
const contentPadding = attrs.contentPadding === "none" ? "none" : definitions.contentPadding.default;
|
|
@@ -748,6 +768,7 @@ function renderSection(
|
|
|
748
768
|
{
|
|
749
769
|
"data-taproot-section": kind,
|
|
750
770
|
"data-content-padding": contentPadding,
|
|
771
|
+
"data-following-spacing": compactFollowing && contentPadding === "standard" ? "compact" : undefined,
|
|
751
772
|
"data-surface": surface,
|
|
752
773
|
"data-entrance": entrance,
|
|
753
774
|
"data-entrance-stagger": entranceStagger,
|
|
@@ -333,7 +333,17 @@ export function getAppearanceReference() {
|
|
|
333
333
|
imageReferences:
|
|
334
334
|
"Use site-owned image ids retained from pull or returned by media upload. URLs are server projections, never authored inputs.",
|
|
335
335
|
logoContract:
|
|
336
|
-
"lightLogoId and darkLogoId are the current scheme-specific logo fields; there is no separate compact-logo setting."
|
|
336
|
+
"lightLogoId and darkLogoId are the current scheme-specific logo fields; there is no separate compact-logo setting. "
|
|
337
|
+
+ "Prefer transparent PNG or WebP artwork with genuine alpha so the header background shows through, including "
|
|
338
|
+
+ "inside letter counters. Avoid baked-in background rectangles unless the brand intentionally uses a badge. "
|
|
339
|
+
+ "Keep padding tight and verify the delivered image preserves transparency. Logo style and coloration are "
|
|
340
|
+
+ "project-specific: choose monochrome, an accent, or multiple colors to suit the brand, not a universal recipe. "
|
|
341
|
+
+ "Keep the lettering readable and test the visible artwork against each header scheme.",
|
|
342
|
+
faviconContract:
|
|
343
|
+
"Include a favicon when designing a site: upload a square, simplified brand mark and set brand.faviconId "
|
|
344
|
+
+ "in settings/brand.json, then run theme push; faviconUrl is a read-only projection. Do not shrink a full wordmark into a favicon. "
|
|
345
|
+
+ "Inspect at 16 and 32 pixels in light and dark browser tabs; a deliberate contrasting badge can help a favicon "
|
|
346
|
+
+ "remain recognizable independently of the header theme. Verify the published icon loads, not just the setting.",
|
|
337
347
|
logoContrastContract:
|
|
338
348
|
"Evaluate lightLogoId against the rendered light-scheme header and darkLogoId against the rendered dark-scheme "
|
|
339
349
|
+ "header at desktop and compact/mobile widths. Transparent pixels provide no contrast: inspect the visible mark, "
|
|
@@ -602,6 +612,7 @@ export function formatPresentationReference(reference) {
|
|
|
602
612
|
+ reference.readOnlyProjections.map((field) => " " + field.file + " :: " + field.path).join("\n")
|
|
603
613
|
+ "\n\nImages: " + reference.imageReferences + "\nLogos: " + reference.logoContract
|
|
604
614
|
+ "\nLogo contrast: " + reference.logoContrastContract
|
|
615
|
+
+ "\nFavicon: " + reference.faviconContract
|
|
605
616
|
+ "\nHeader width: " + reference.headerWidthContract
|
|
606
617
|
+ "\nMobile menu and menu font: " + reference.menuContract
|
|
607
618
|
+ "\nFooter: " + reference.footerBoundary
|
package/src/reference-help.js
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
|
|
52
52
|
export { getAppearanceReference, getFooterReference, getThemeReference };
|
|
53
53
|
|
|
54
|
-
export const REFERENCE_VERSION =
|
|
54
|
+
export const REFERENCE_VERSION = 24;
|
|
55
55
|
export const PAGE_TYPES = Object.freeze(["free-form"]);
|
|
56
56
|
|
|
57
57
|
function deepFreeze(value) {
|
|
@@ -134,7 +134,7 @@ const DESIGN_BLUEPRINTS = deepFreeze([
|
|
|
134
134
|
purpose: "Make a single product story legible before offering supporting choices.",
|
|
135
135
|
section: { entrance: "scale", entranceStagger: "none" },
|
|
136
136
|
components: [
|
|
137
|
-
{ type: "image-banner", properties: { ratio: "3/1", heightMode: "viewport", contentPosition: "bottom-start", scrim: "bottom", imageMotion: "
|
|
137
|
+
{ type: "image-banner", properties: { ratio: "3/1", heightMode: "viewport", contentPosition: "bottom-start", scrim: "bottom", imageMotion: "parallax" } },
|
|
138
138
|
{ type: "cta", properties: { variant: "primary", imagePosition: "right", borderWidth: 0 } },
|
|
139
139
|
],
|
|
140
140
|
},
|
|
@@ -148,7 +148,7 @@ const DESIGN_BLUEPRINTS = deepFreeze([
|
|
|
148
148
|
],
|
|
149
149
|
},
|
|
150
150
|
],
|
|
151
|
-
motionCeiling: "
|
|
151
|
+
motionCeiling: "Prefer one optional image parallax per view (imageMotion: parallax-subtle or parallax). Published pages use CSS view timelines where supported and a platform-owned scroll-linked JavaScript fallback otherwise; no custom code is needed. Movement stops when scrolling stops, pauses offscreen, and is disabled for reduced motion. Without JavaScript or native timelines the image stays static. Cinematic zoom is an explicit ambient-loop alternative. Never conceal names, price context, or actions behind motion.",
|
|
152
152
|
mediaBrief: "Commission or license product, material, and in-use photography; document crop-safe banner areas and supply alt text for informative images.",
|
|
153
153
|
integrationBoundaries: "Use outbound links for retail, commerce, search, or booking providers. This blueprint includes none of those systems and no custom code.",
|
|
154
154
|
accessibilityCautions: ["Preserve text contrast over imagery with a tested scrim and do not make text depend on hover.", "Respect reduced-motion preferences and give every campaign action a visible text label."],
|
|
@@ -511,6 +511,7 @@ const WORKFLOW_REFERENCES = Object.freeze({
|
|
|
511
511
|
details: Object.freeze([
|
|
512
512
|
"Paths are relative to the configured workspace root, not the shell's current directory.",
|
|
513
513
|
"PNG, JPEG, GIF, and WebP are accepted; retina names such as logo@2x.png and logo@3x.png are supported.",
|
|
514
|
+
"For header logos prefer genuine transparent PNG/WebP, check contrast in both themes, and include a simplified square favicon. See help appearance for branding guidance.",
|
|
514
515
|
"Each result item includes media: { imageId, src, urls, width, height, alt }.",
|
|
515
516
|
"The same src/urls delivery fields are saved in .taproot-site-media.json for page and component authoring.",
|
|
516
517
|
]),
|