efficient-web-builder 19.1.0 → 22.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.
Files changed (139) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/dist/components/AnimatedStat.js +1 -1
  3. package/dist/components/Banner.js +1 -1
  4. package/dist/components/Cards.js +1 -1
  5. package/dist/components/CategoryBar.js +1 -1
  6. package/dist/components/ChartSection.js +1 -1
  7. package/dist/components/ChatSettingsPanel.js +1 -1
  8. package/dist/components/ContactLocations.js +3 -3
  9. package/dist/components/Countdown.js +1 -1
  10. package/dist/components/DealerFinder.d.ts +12 -0
  11. package/dist/components/DealerFinder.js +1 -0
  12. package/dist/components/DocMasthead.js +1 -1
  13. package/dist/components/DocMetaCards.js +1 -1
  14. package/dist/components/DocTabs.js +1 -1
  15. package/dist/components/FAQ.js +1 -1
  16. package/dist/components/FeaturedCover.js +1 -1
  17. package/dist/components/Footprint.d.ts +8 -1
  18. package/dist/components/Footprint.js +5 -5
  19. package/dist/components/HeroPartPanel.d.ts +7 -0
  20. package/dist/components/HeroPartPanel.js +1 -0
  21. package/dist/components/HeroPoster.d.ts +2 -1
  22. package/dist/components/HeroPoster.js +1 -1
  23. package/dist/components/HeroSingle.d.ts +12 -1
  24. package/dist/components/HeroSingle.js +11 -1
  25. package/dist/components/HeroSlide.d.ts +2 -1
  26. package/dist/components/HeroSlide.js +1 -1
  27. package/dist/components/HeroVideoPlay.d.ts +13 -0
  28. package/dist/components/HeroVideoPlay.js +1 -0
  29. package/dist/components/ImageBlock.d.ts +2 -1
  30. package/dist/components/ImageBlock.js +1 -1
  31. package/dist/components/KeyFeatures.js +1 -1
  32. package/dist/components/LocationGroupHeader.js +1 -1
  33. package/dist/components/Logos.js +1 -1
  34. package/dist/components/LongRead.js +1 -1
  35. package/dist/components/Manifesto.js +1 -1
  36. package/dist/components/Masthead.js +1 -1
  37. package/dist/components/MediaUploadField.d.ts +3 -1
  38. package/dist/components/MediaUploadField.js +1 -1
  39. package/dist/components/MockupSection.js +1 -1
  40. package/dist/components/OrderSummary.js +1 -1
  41. package/dist/components/PagesPanel.js +1 -1
  42. package/dist/components/ParagraphsA.js +1 -1
  43. package/dist/components/PostCard.js +1 -1
  44. package/dist/components/PreviewChatContext.d.ts +8 -0
  45. package/dist/components/PreviewChatContext.js +1 -1
  46. package/dist/components/PreviewHeroPartContext.d.ts +3 -0
  47. package/dist/components/PreviewHeroPartContext.js +1 -0
  48. package/dist/components/ProcessSteps.js +1 -1
  49. package/dist/components/Prose.js +1 -1
  50. package/dist/components/PublishedPartStylesContext.js +1 -1
  51. package/dist/components/Reel.js +1 -1
  52. package/dist/components/SectionEditor.d.ts +10 -0
  53. package/dist/components/SectionEditor.js +1 -1
  54. package/dist/components/SectionEyebrow.js +1 -1
  55. package/dist/components/SectionHeader.js +1 -1
  56. package/dist/components/SectionIcon.js +1 -1
  57. package/dist/components/Series.js +1 -1
  58. package/dist/components/SiteFields.d.ts +3 -1
  59. package/dist/components/SiteFields.js +1 -1
  60. package/dist/components/SiteTextStylesField.js +1 -1
  61. package/dist/components/Split.js +1 -1
  62. package/dist/components/StandaloneSectionEditor.js +1 -1
  63. package/dist/components/Stats.js +1 -1
  64. package/dist/components/StatsParagraph.js +1 -1
  65. package/dist/components/StorefrontEditor.js +1 -1
  66. package/dist/components/Testimonials.js +1 -1
  67. package/dist/components/Text.js +1 -1
  68. package/dist/components/ThemeField.js +1 -1
  69. package/dist/components/Timeline.js +1 -1
  70. package/dist/components/TimelineTrack.js +1 -1
  71. package/dist/components/WebsiteScreenParts.js +1 -1
  72. package/dist/components/storefront-editor.css +103 -302
  73. package/dist/components/useLocationsTree.js +1 -1
  74. package/dist/lib/content/chat-launcher.d.ts +9 -0
  75. package/dist/lib/content/chat-launcher.js +1 -0
  76. package/dist/lib/content/page-width.d.ts +1 -0
  77. package/dist/lib/content/page-width.js +1 -1
  78. package/dist/lib/content/phone-focus.d.ts +7 -0
  79. package/dist/lib/content/phone-focus.js +1 -0
  80. package/dist/lib/dealer-source.d.ts +34 -0
  81. package/dist/lib/dealer-source.js +1 -0
  82. package/dist/lib/editor-words-catalogue.d.ts +23 -7
  83. package/dist/lib/editor-words-catalogue.js +4 -4
  84. package/dist/lib/hero-arrows.d.ts +1 -1
  85. package/dist/lib/hero-media-layout.d.ts +11 -0
  86. package/dist/lib/hero-media-layout.js +1 -0
  87. package/dist/lib/hero-video.d.ts +15 -0
  88. package/dist/lib/hero-video.js +1 -0
  89. package/dist/lib/links/phone-href.d.ts +1 -0
  90. package/dist/lib/links/phone-href.js +1 -0
  91. package/dist/lib/markup.d.ts +5 -3
  92. package/dist/lib/markup.js +2 -2
  93. package/dist/lib/preview-header-settings.d.ts +1 -0
  94. package/dist/lib/puck/MarkupToolbar.d.ts +1 -1
  95. package/dist/lib/puck/MarkupToolbar.js +1 -1
  96. package/dist/lib/puck/blocks/general/Footprint/fields.js +1 -1
  97. package/dist/lib/puck/blocks/general/Footprint/render.js +2 -2
  98. package/dist/lib/puck/blocks/general/Hero/fields.js +1 -1
  99. package/dist/lib/puck/blocks/general/HeroSingle/fields.js +1 -1
  100. package/dist/lib/puck/blocks/general/HeroSingle/render.js +1 -1
  101. package/dist/lib/puck/blocks/general/HeroSlide/fields.js +1 -1
  102. package/dist/lib/puck/blocks/general/HeroSlide/render.js +1 -1
  103. package/dist/lib/puck/blocks/general/ImageBlock/fields.js +1 -1
  104. package/dist/lib/puck/blocks/general/ImageBlock/render.js +1 -1
  105. package/dist/lib/puck/canvas-typing.d.ts +1 -2
  106. package/dist/lib/puck/controls.d.ts +49 -2
  107. package/dist/lib/puck/controls.js +1 -1
  108. package/dist/lib/puck/field-groups.js +1 -1
  109. package/dist/lib/puck/hero-media-parts.d.ts +42 -0
  110. package/dist/lib/puck/hero-media-parts.js +1 -0
  111. package/dist/lib/puck/media-manifest.json +3 -1
  112. package/dist/lib/puck/part-styles.js +2 -2
  113. package/dist/lib/puck/passes/settings-groups.js +1 -1
  114. package/dist/lib/reel.d.ts +3 -3
  115. package/dist/lib/reel.js +1 -1
  116. package/dist/lib/sections/collection-card-text-regions.d.ts +9 -1
  117. package/dist/lib/sections/collection-card-text-regions.js +1 -1
  118. package/dist/lib/sections/text-regions.d.ts +1 -0
  119. package/dist/lib/sections/text-regions.js +1 -1
  120. package/dist/lib/site-text-styles.d.ts +1 -0
  121. package/dist/lib/site-text-styles.js +2 -2
  122. package/dist/lib/source-rules/heading-ceiling-sweep.js +3 -3
  123. package/dist/lib/source-rules/one-markup-grammar.d.ts +5 -0
  124. package/dist/lib/source-rules/one-markup-grammar.js +4 -0
  125. package/dist/lib/source-rules/one-site-column.d.ts +6 -0
  126. package/dist/lib/source-rules/one-site-column.js +3 -0
  127. package/dist/lib/source-rules/part-style-naming.js +2 -2
  128. package/dist/lib/source-rules/rule.d.ts +1 -0
  129. package/dist/lib/source-rules/rule.js +3 -1
  130. package/dist/lib/source-rules/size-token-paints.js +4 -4
  131. package/dist/lib/source-rules/style-face-matches-size.js +3 -3
  132. package/dist/lib/web-backend.d.ts +5 -0
  133. package/package.json +7 -5
  134. package/dist/components/ImageFocusRow.d.ts +0 -7
  135. package/dist/components/ImageFocusRow.js +0 -1
  136. package/dist/components/TemplateGlyph.d.ts +0 -6
  137. package/dist/components/TemplateGlyph.js +0 -1
  138. package/dist/components/TextStyleRow.d.ts +0 -13
  139. package/dist/components/TextStyleRow.js +0 -1
@@ -1,11 +1,11 @@
1
1
  import { type HeroGroupPlacement } from "./hero-group-placement.js";
2
2
  export declare const REEL_PLACEMENTS: readonly ["top-left", "top-center", "top-right", "left", "center", "right", "bottom-left", "bottom-center", "bottom-right"];
3
3
  export type ReelPlacement = HeroGroupPlacement;
4
- export declare const REEL_PLACEMENT_LABELS: Readonly<Record<"center" | "left" | "top-left" | "top-center" | "top-right" | "right" | "bottom-left" | "bottom-center" | "bottom-right", string>>;
4
+ export declare const REEL_PLACEMENT_LABELS: Readonly<Record<"center" | "left" | "right" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right", string>>;
5
5
  export declare const REEL_PLACEMENT_DEFAULT: ReelPlacement;
6
- export declare const REEL_SHAPES: readonly ["cards", "dots", "numbers", "bar", "tabs"];
6
+ export declare const REEL_SHAPES: readonly ["cards", "dots", "numbers", "bar", "squares", "tabs"];
7
7
  export type ReelShape = (typeof REEL_SHAPES)[number];
8
- export declare const HERO_REEL_SHAPES: readonly ["cards", "dots", "numbers", "bar"];
8
+ export declare const HERO_REEL_SHAPES: readonly ["cards", "dots", "numbers", "bar", "squares"];
9
9
  export type HeroReelShape = (typeof HERO_REEL_SHAPES)[number];
10
10
  export declare const REEL_SHAPE_DEFAULT: HeroReelShape;
11
11
  export declare const REEL_SHAPE_LABELS: Readonly<Record<ReelShape, string>>;
package/dist/lib/reel.js CHANGED
@@ -1 +1 @@
1
- import{HERO_GROUP_PLACEMENTS as E,HERO_GROUP_PLACEMENT_LABELS as c}from"./hero-group-placement.js";import{heroOverlayRailClass as l}from"./hero-overlay-anchor.js";const u=E,_=c,i="top-right",d=["cards","dots","numbers","bar","tabs"],p=["cards","dots","numbers","bar"],s="cards",A={cards:"Cards",dots:"Dots",numbers:"Numbers",bar:"Bar",tabs:"Tabs"},S=!0;function n(e){return d.includes(e)?e:s}function P(e){return p.includes(e)?e:s}function f(e){return u.includes(e)?e:i}const a={cards:"flex-col gap-2.5",dots:"flex-row items-center gap-2",numbers:"flex-col gap-2",bar:"h-0.5 w-[34%] max-w-[420px] gap-px overflow-hidden rounded-full",tabs:"flex-row items-stretch w-full overflow-x-auto"};function h(e,r){return l(f(r),a[n(e)])}function H(e){return`flex ${a[n(e)]}`}function b(e){return e.shown!==!1&&(e.count??0)>1}function x(e){return{name:e.dataset.slideName||"",process:e.dataset.slideProcess||"",video:e.dataset.slideMedia==="video"}}function m(e,r){return e===r?!0:e.length!==r.length?!1:e.every((t,o)=>t.name===r[o].name&&t.process===r[o].process&&t.video===r[o].video)}function w(e,r){const t=Array.from(r,x);return m(e,t)?e:t}export{p as HERO_REEL_SHAPES,u as REEL_PLACEMENTS,i as REEL_PLACEMENT_DEFAULT,_ as REEL_PLACEMENT_LABELS,d as REEL_SHAPES,s as REEL_SHAPE_DEFAULT,A as REEL_SHAPE_LABELS,S as REEL_SHOWN_DEFAULT,w as nextReel,P as normaliseHeroReelShape,f as normaliseReelPlacement,n as normaliseReelShape,h as reelRailClass,b as reelShown,H as reelSurfaceRailClass,m as sameReel};
1
+ import{HERO_GROUP_PLACEMENTS as l,HERO_GROUP_PLACEMENT_LABELS as E}from"./hero-group-placement.js";import{heroOverlayRailClass as c}from"./hero-overlay-anchor.js";const u=l,_=E,i="top-right",d=["cards","dots","numbers","bar","squares","tabs"],p=["cards","dots","numbers","bar","squares"],s="cards",S={cards:"Cards",dots:"Dots",numbers:"Numbers",bar:"Bar",squares:"Squares",tabs:"Tabs"},A=!0;function n(e){return d.includes(e)?e:s}function P(e){return p.includes(e)?e:s}function f(e){return u.includes(e)?e:i}const a={cards:"flex-col gap-2.5",dots:"flex-row items-center gap-2",numbers:"flex-col gap-2",bar:"h-0.5 w-[34%] max-w-[420px] gap-px overflow-hidden rounded-full",squares:"flex-col gap-0",tabs:"flex-row items-stretch w-full overflow-x-auto"};function h(e,r){return c(f(r),a[n(e)])}function H(e){return`flex ${a[n(e)]}`}function b(e){return e.shown!==!1&&(e.count??0)>1}function x(e){return{name:e.dataset.slideName||"",process:e.dataset.slideProcess||"",video:e.dataset.slideMedia==="video"}}function m(e,r){return e===r?!0:e.length!==r.length?!1:e.every((t,o)=>t.name===r[o].name&&t.process===r[o].process&&t.video===r[o].video)}function w(e,r){const t=Array.from(r,x);return m(e,t)?e:t}export{p as HERO_REEL_SHAPES,u as REEL_PLACEMENTS,i as REEL_PLACEMENT_DEFAULT,_ as REEL_PLACEMENT_LABELS,d as REEL_SHAPES,s as REEL_SHAPE_DEFAULT,S as REEL_SHAPE_LABELS,A as REEL_SHOWN_DEFAULT,w as nextReel,P as normaliseHeroReelShape,f as normaliseReelPlacement,n as normaliseReelShape,h as reelRailClass,b as reelShown,H as reelSurfaceRailClass,m as sameReel};
@@ -1,4 +1,12 @@
1
- import type { TextRegions } from "./text-regions.js";
1
+ import { type TextRegions } from "./text-regions.js";
2
2
  export type CollectionCardRegionKey = "tag" | "name" | "body" | "price" | "link";
3
+ export declare const COLLECTION_CARD_TEXT_SIZE: {
4
+ readonly tag: "text-[length:var(--size-label,13px)]";
5
+ readonly name: "text-[length:min(var(--size-headingMedium,48px),var(--sf-heading-max,9999px))]";
6
+ readonly namePhone: "max-[800px]:text-[length:min(var(--size-headingMedium,36px),var(--sf-heading-max,9999px))]";
7
+ readonly body: "text-[length:var(--size-body,14px)]";
8
+ readonly price: "text-[length:var(--size-label,15px)]";
9
+ readonly link: "text-[length:var(--size-label,14px)]";
10
+ };
3
11
  export declare const COLLECTION_CARD_REGIONS: Readonly<Record<CollectionCardRegionKey, TextRegions[string]>>;
4
12
  export declare const COLLECTION_CARD_TEXT_REGIONS: TextRegions;
@@ -1 +1 @@
1
- const l={tag:{label:"Tag",follows:"Labels",builtIn:"13px",writable:!0},name:{label:"Name",follows:"Heading 2",builtIn:"48px",writable:!0},body:{label:"Body",follows:"Body",builtIn:"14px",writable:!0},price:{label:"Price",follows:"Labels",builtIn:"15px",writable:!0},link:{label:"Link",follows:"Labels",builtIn:"14px",writable:!0}},e=l;export{l as COLLECTION_CARD_REGIONS,e as COLLECTION_CARD_TEXT_REGIONS};
1
+ import{builtInOf as e}from"./text-regions.js";const l={tag:"text-[length:var(--size-label,13px)]",name:"text-[length:min(var(--size-headingMedium,48px),var(--sf-heading-max,9999px))]",namePhone:"max-[800px]:text-[length:min(var(--size-headingMedium,36px),var(--sf-heading-max,9999px))]",body:"text-[length:var(--size-body,14px)]",price:"text-[length:var(--size-label,15px)]",link:"text-[length:var(--size-label,14px)]"},t={tag:{label:"Tag",follows:"Labels",builtIn:e(l.tag),writable:!0},name:{label:"Name",follows:"Heading 2",builtIn:e(l.name),writable:!0},body:{label:"Body",follows:"Body",builtIn:e(l.body),writable:!0},price:{label:"Price",follows:"Labels",builtIn:e(l.price),writable:!0},link:{label:"Link",follows:"Labels",builtIn:e(l.link),writable:!0}},i=t;export{t as COLLECTION_CARD_REGIONS,i as COLLECTION_CARD_TEXT_REGIONS,l as COLLECTION_CARD_TEXT_SIZE};
@@ -11,6 +11,7 @@ export declare function regionStyle(role: string | null | undefined, region: Tex
11
11
  fontSize: string;
12
12
  } | undefined;
13
13
  export type TextRegions = Readonly<Record<string, TextRegion>>;
14
+ export declare function builtInOf(sizeClass: string): string;
14
15
  export declare function textRegionPartKey(key: string): string;
15
16
  export declare function textRegionOfPartKey(regions: TextRegions, propKey: string): string | null;
16
17
  export declare function textRegionOfAttr(regions: TextRegions, value: string | null): string | null;
@@ -1 +1 @@
1
- const i="data-sf-spec-region",u="@";function f(t,n,e){if(!t)return;const r=e[t];if(r)return{fontFamily:`var(--font-role-${t})`,fontSize:`var(--size-${r}, ${n.builtIn})`}}function s(t){return`@${t}`}function c(t,n){if(!n.startsWith("@"))return null;const e=n.slice(1);return e in t?e:null}function o(t,n){return n&&n in t?n:null}function l(t,n){const e=n?.closest(`[${i}]`)?.getAttribute(i);return o(t,e??null)}export{i as TEXT_REGION_ATTR,u as TEXT_REGION_PREFIX,f as regionStyle,l as textRegionForClick,o as textRegionOfAttr,c as textRegionOfPartKey,s as textRegionPartKey};
1
+ const i="data-sf-spec-region",u="@";function f(t,n,e){if(!t)return;const r=e[t];if(r)return{fontFamily:`var(--font-role-${t})`,fontSize:`var(--size-${r}, ${n.builtIn})`}}function c(t){const n=/var\(--size-[A-Za-z]+, ?([0-9.]+px)\)/.exec(t);if(!n)throw new Error(`builtInOf: no --size-* fallback in ${t}`);return n[1]}function l(t){return`@${t}`}function s(t,n){if(!n.startsWith("@"))return null;const e=n.slice(1);return e in t?e:null}function o(t,n){return n&&n in t?n:null}function E(t,n){const e=n?.closest(`[${i}]`)?.getAttribute(i);return o(t,e??null)}export{i as TEXT_REGION_ATTR,u as TEXT_REGION_PREFIX,c as builtInOf,f as regionStyle,E as textRegionForClick,o as textRegionOfAttr,s as textRegionOfPartKey,l as textRegionPartKey};
@@ -14,6 +14,7 @@ export type StoredTextStyleValues = {
14
14
  paragraphSpacing?: number;
15
15
  letterCase?: TextStyleCase;
16
16
  largestSize?: number;
17
+ sizePhone?: number;
17
18
  };
18
19
  export type StoredTextStyle = {
19
20
  id: string;
@@ -1,2 +1,2 @@
1
- import{FONT_OPTIONS as D,normalizeFonts as I}from"./fonts.js";import{partSpacingOf as J}from"./puck/part-styles.js";import{compressionMetrics as B,headingTypographyFromRow as G,normalizeHeadingCompression as C,normalizeSizes as L}from"./theme.js";const H=e=>e==="sentence"?"none":void 0,p=e=>typeof e=="number"&&Number.isFinite(e)?e:void 0,z=e=>typeof e=="string"&&e?e:void 0,M=["font","color","size","weight","italic","lineHeight","letterSpacing","paragraphSpacing","letterCase","largestSize"];function g(e){const i={};for(const n of M)e[n]!==void 0&&(i[n]=e[n]);return i}function w(e,i){const{auto:n,...t}=e,a=new Set(i??[]),s=M.filter(o=>a.has(o)&&e.values[o]===void 0);return s.length?{...t,auto:s}:t}function O(e){const i=e.fonts,n=e.sizes,t=e.typography,a=B(e.compression),s=(l,r)=>({id:`heading-${l}`,kind:"heading",name:`Heading ${l}`,values:g(r)}),o=(l,r={})=>({id:`body-${l}`,kind:"body",name:`Body ${l}`,values:g(r)});return[s(1,{font:z(i.heading),size:p(n.headingLarge),weight:p(t.weight),lineHeight:a.lineHeight,letterSpacing:a.letterSpacing,letterCase:H(t.case),largestSize:p(t.maxPx)}),s(2,{font:z(i.heading2),size:p(n.headingMedium),weight:p(t.weight2),lineHeight:a.lineHeight,letterSpacing:a.letterSpacing,letterCase:H(t.case2)}),s(3,{font:z(i.heading3),size:p(n.headingSmall),weight:p(t.weight3),lineHeight:a.lineHeight,letterSpacing:a.letterSpacing,letterCase:H(t.case3)}),o(1,{font:z(i.body),size:p(n.body)}),o(2),o(3),o(4),o(5,{font:z(i.label),size:p(n.label)})]}function _(e){return Array.isArray(e)&&e.every(n=>n&&typeof n=="object"&&typeof n.id=="string"&&(n.kind==="heading"||n.kind==="body")&&typeof n.name=="string")?e.map(n=>w({...n,values:g(n.values??{})},Array.isArray(n.auto)?n.auto:void 0)):null}function ne(e){return _(e)??[]}function T(e,i){const n=O(i),t=_(e);return t?t.map(a=>{const s={...N(n,a.id)};for(const o of a.auto??[])delete s[o];return{...a,values:g({...s,...a.values})}}):n}const N=(e,i)=>e.find(n=>n.id===i)?.values??{},P=["heading-1","heading-2","heading-3"];function te({saved:e,old:i,cur:n,next:t,sizeOf:a}){const s=O(i),o=_(e)??s.map(d=>({...d,values:{}})),l=T(o,i),r=[],h=t.map(d=>{const f=n.find(u=>u.id===d.id),m=o.find(u=>u.id===d.id);if(!f||!m)return U([d],a)[0];const c={...m.values},x=new Set(m.auto??[]),v=d.values,A=f.values,j=[...new Set([...Object.keys(v),...Object.keys(A)])].filter(u=>JSON.stringify(v[u])!==JSON.stringify(A[u]));j.sort((u,y)=>+(u==="letterSpacing")-+(y==="letterSpacing"));for(const u of j){const y=u==="lineSpacing"?"lineHeight":u,k=v[u];if(k===void 0){delete c[y],N(s,d.id)[y]!==void 0&&(r.push([d.id,y]),x.add(y));continue}if(x.delete(y),u==="lineSpacing")c.lineHeight=S(k/100,3);else if(u==="letterSpacing"){const E=l.find(V=>V.id===d.id),F=a({...d,values:g({...E?.values??{},...c})});c.letterSpacing=F?S(k/F,4):0}else c[y]=k}return w({id:d.id,kind:d.kind,name:d.name,values:g(c)},x)});let b=i.compression;if(r.some(([d,f])=>(f==="lineHeight"||f==="letterSpacing")&&P.includes(d))&&C(b)!=="normal"){for(const d of P){const f=h.find(c=>c.id===d);if(!f)continue;const m={...f.values};for(const c of["lineHeight","letterSpacing"])r.some(([x,v])=>x===d&&v===c)||m[c]===void 0&&!(f.auto??[]).includes(c)&&(m[c]=N(s,d)[c]);f.values=g(m)}b="normal"}return{saved:h,old:{...i,compression:b}}}function ie(e,i){const n={text_styles:e.saved};return e.old.compression!==i.compression&&(n.heading_compression=C(e.old.compression)),n}const S=(e,i)=>Number(e.toFixed(i));function ae(e,i){return e.map(n=>{const{lineHeight:t,letterSpacing:a,...s}=n.values,o={...s};return t!==void 0&&(o.lineSpacing=S(t*100,1)),a!==void 0&&(o.letterSpacing=S(a*i(n),2)),{id:n.id,kind:n.kind,name:n.name,values:o}})}function U(e,i){return e.map(n=>{const{lineSpacing:t,letterSpacing:a,...s}=n.values,o={id:n.id,kind:n.kind,name:n.name,values:g({...s})},l={...o.values};if(t!==void 0&&(l.lineHeight=S(t/100,3)),a!==void 0){const r=i({...o,values:{...o.values}});l.letterSpacing=r?S(a/r,4):0}return{...o,values:g(l)}})}function oe(e){const i={fonts:I({heading:e.heading_font??void 0,heading2:e.heading_2_font??void 0,heading3:e.heading_3_font??void 0,body:e.body_font??void 0,accent:e.accent_font??void 0,label:e.label_font??void 0}),sizes:L({headingLarge:e.heading_size_large_px,headingMedium:e.heading_size_medium_px,headingSmall:e.heading_size_small_px,accent:e.accent_size_px,body:e.body_size_px,label:e.label_size_px}),compression:C(e.heading_compression),typography:G(e)},n=e.text_styles??null,t=Array.isArray(n)?R(T(n,i),i):i;return{fonts:I(t.fonts),sizes:L(t.sizes),compression:C(t.compression),typography:t.typography,textStyles:n}}function R(e,i){const n=d=>e.find(f=>f.id===d)?.values??{},t=n("heading-1"),a=n("heading-2"),s=n("heading-3"),o=n("body-1"),l=n("body-5"),r=i.fonts,h=i.sizes,b=i.typography,$=d=>d==="none"?"sentence":"authored";return{fonts:{...r,heading:t.font??r.heading,heading2:a.font??t.font??r.heading2,heading3:s.font??t.font??r.heading3,body:o.font??r.body,label:l.font??r.label},sizes:{...h,headingLarge:t.size??null,headingMedium:a.size??null,headingSmall:s.size??null,body:o.size??null,label:l.size??null},compression:i.compression,typography:{...b,case:$(t.letterCase),weight:t.weight??null,maxPx:t.largestSize??null,weight2:a.weight??null,weight3:s.weight??null,case2:$(a.letterCase),case3:$(s.letterCase)}}}function se(e){const i=_(e.textStyles);if(!i||i.every(a=>Object.keys(a.values).length===0&&!a.auto))return e;const n={fonts:e.fonts??{},sizes:e.sizes??{},compression:String(e.headingCompression??"normal"),typography:e.headingTypography??{}},t=R(T(i,n),n);return{...e,fonts:t.fonts,sizes:t.sizes,headingCompression:t.compression,headingTypography:t.typography}}function W(e){switch(e.id){case"heading-1":return"font-display";case"heading-2":return"font-heading2";case"heading-3":return"font-heading3";case"body-1":return"font-body";case"body-5":return"font-mono";default:return`font-${e.id}`}}const q={none:"none",upper:"uppercase",capitalize:"capitalize",lower:"lowercase"};function K(e){return e==="accent"?"var(--accent)":e==="text"?"var(--fg)":e==="labels"?"var(--fg-dim)":/^#[0-9a-f]{6}$/i.test(e)?e:null}function Q(e){const i=":is([data-sf-bullet], [data-sf-number])";return[`${e} br:not(br + br) { display: block; content: ""; margin-top: var(--sf-part-para) }`,`${e} ${i}:not(:first-child) { margin-top: var(--sf-part-para) }`,`${e} ${i}:not(:last-child) { margin-bottom: var(--sf-part-para) }`]}function re(e){const i=[];for(const n of e){const t=n.values,a=`.${W(n)}`,s=[],o=t.font!==void 0?D.find(h=>h.key===t.font)?.cssVar:void 0;if(o&&s.push(`font-family: var(${o})`),t.size!==void 0){const h=t.largestSize!==void 0?Math.min(t.size,t.largestSize):t.size;s.push(`font-size: ${h}px`)}const l=t.color!==void 0?K(t.color):null;l&&s.push(`color: ${l}`),t.italic===!0&&s.push("font-style: italic"),t.weight!==void 0&&s.push(`font-weight: ${t.weight}`);const r=[];t.lineHeight!==void 0&&r.push(`line-height: ${t.lineHeight}`),t.letterSpacing!==void 0&&r.push(`letter-spacing: ${t.letterSpacing}em`),n.kind==="body"&&s.push(...r),t.letterCase!==void 0&&s.push(`text-transform: ${q[t.letterCase]}`),t.paragraphSpacing&&s.push(`--sf-part-para: ${t.paragraphSpacing}px`),s.length&&i.push(`${a} { ${s.join("; ")} }`),n.kind==="heading"&&r.length&&i.push(`${a}, ${a}:is(h1, h2, h3, h4) { ${r.join("; ")} }`),t.paragraphSpacing&&i.push(...Q(a))}return i.join(`
2
- `)}function de(e){return e!==!0&&e!==!1?"":`em.text-accent { font-style: ${e?"italic":"normal"} }`}function le(e){switch(e){case"heading":return"heading-1";case"heading2":return"heading-2";case"heading3":return"heading-3";case"body":return"body-1";case"label":return"body-5";default:return}}function X(e){const i=J(e);return g({font:e.font,color:e.color,size:e.size,weight:e.weight,italic:e.italic,lineHeight:i.lineHeight,letterSpacing:i.letterSpacingEm,paragraphSpacing:e.paragraphSpacing,letterCase:e.case})}function ce(e){return Object.keys(X(e)).length>0}function ue({saved:e,old:i,styleId:n,values:t}){return{saved:(_(e)??O(i).map(o=>({...o,values:{}}))).map(o=>o.id===n?w({...o,values:g({...o.values,...t})},o.auto):o),old:i}}function fe(e){const i=e.filter(n=>n.kind==="body").map(n=>Number(/^body-(\d+)$/.exec(n.id)?.[1])).filter(n=>Number.isInteger(n)&&n>0);return i.length?`body-${Math.max(...i)}`:"body-1"}export{de as accentItalicCss,te as applyListChange,fe as eyebrowStyleId,W as faceClassOf,U as fromPanelStyles,ce as hasStyleChange,ie as listChangePatch,X as partStyleToTextStyleValues,ue as pushIntoStyle,T as resolveTextStyles,oe as siteTextFromRow,ne as storedTextStyles,le as styleIdOfRole,re as textStylesCss,O as textStylesFromOld,R as textStylesToOld,se as themeWithTextStyles,ae as toPanelStyles};
1
+ import{FONT_OPTIONS as D,normalizeFonts as F}from"./fonts.js";import{PART_PHONE_MAX_CSS as J,partSpacingOf as B}from"./puck/part-styles.js";import{compressionMetrics as G,headingTypographyFromRow as U,normalizeHeadingCompression as C,normalizeSizes as I}from"./theme.js";const H=e=>e==="sentence"?"none":void 0,h=e=>typeof e=="number"&&Number.isFinite(e)?e:void 0,v=e=>typeof e=="string"&&e?e:void 0,E=["font","color","size","sizePhone","weight","italic","lineHeight","letterSpacing","paragraphSpacing","letterCase","largestSize"];function p(e){const i={};for(const n of E)e[n]!==void 0&&(i[n]=e[n]);return i}function w(e,i){const{auto:n,...t}=e,a=new Set(i??[]),s=E.filter(o=>a.has(o)&&e.values[o]===void 0);return s.length?{...t,auto:s}:t}function O(e){const i=e.fonts,n=e.sizes,t=e.typography,a=G(e.compression),s=(l,r)=>({id:`heading-${l}`,kind:"heading",name:`Heading ${l}`,values:p(r)}),o=(l,r={})=>({id:`body-${l}`,kind:"body",name:`Body ${l}`,values:p(r)});return[s(1,{font:v(i.heading),size:h(n.headingLarge),weight:h(t.weight),lineHeight:a.lineHeight,letterSpacing:a.letterSpacing,letterCase:H(t.case),largestSize:h(t.maxPx)}),s(2,{font:v(i.heading2),size:h(n.headingMedium),weight:h(t.weight2),lineHeight:a.lineHeight,letterSpacing:a.letterSpacing,letterCase:H(t.case2)}),s(3,{font:v(i.heading3),size:h(n.headingSmall),weight:h(t.weight3),lineHeight:a.lineHeight,letterSpacing:a.letterSpacing,letterCase:H(t.case3)}),o(1,{font:v(i.body),size:h(n.body)}),o(2),o(3),o(4),o(5,{font:v(i.label),size:h(n.label)})]}function _(e){return Array.isArray(e)&&e.every(n=>n&&typeof n=="object"&&typeof n.id=="string"&&(n.kind==="heading"||n.kind==="body")&&typeof n.name=="string")?e.map(n=>w({...n,values:p(n.values??{})},Array.isArray(n.auto)?n.auto:void 0)):null}function ie(e){return _(e)??[]}function T(e,i){const n=O(i),t=_(e);return t?t.map(a=>{const s={...P(n,a.id)};for(const o of a.auto??[])delete s[o];return{...a,values:p({...s,...a.values})}}):n}const P=(e,i)=>e.find(n=>n.id===i)?.values??{},M=["heading-1","heading-2","heading-3"];function ae({saved:e,old:i,cur:n,next:t,sizeOf:a}){const s=O(i),o=_(e)??s.map(d=>({...d,values:{}})),l=T(o,i),r=[],g=t.map(d=>{const f=n.find(u=>u.id===d.id),S=o.find(u=>u.id===d.id);if(!f||!S)return W([d],a)[0];const c={...S.values},x=new Set(S.auto??[]),z=d.values,A=f.values,N=[...new Set([...Object.keys(z),...Object.keys(A)])].filter(u=>JSON.stringify(z[u])!==JSON.stringify(A[u]));N.sort((u,m)=>+(u==="letterSpacing")-+(m==="letterSpacing"));for(const u of N){const m=u==="lineSpacing"?"lineHeight":u,k=z[u];if(k===void 0){delete c[m],P(s,d.id)[m]!==void 0&&(r.push([d.id,m]),x.add(m));continue}if(x.delete(m),u==="lineSpacing")c.lineHeight=b(k/100,3);else if(u==="letterSpacing"){const R=l.find(V=>V.id===d.id),j=a({...d,values:p({...R?.values??{},...c})});c.letterSpacing=j?b(k/j,4):0}else c[m]=k}return w({id:d.id,kind:d.kind,name:d.name,values:p(c)},x)});let y=i.compression;if(r.some(([d,f])=>(f==="lineHeight"||f==="letterSpacing")&&M.includes(d))&&C(y)!=="normal"){for(const d of M){const f=g.find(c=>c.id===d);if(!f)continue;const S={...f.values};for(const c of["lineHeight","letterSpacing"])r.some(([x,z])=>x===d&&z===c)||S[c]===void 0&&!(f.auto??[]).includes(c)&&(S[c]=P(s,d)[c]);f.values=p(S)}y="normal"}return{saved:g,old:{...i,compression:y}}}function oe(e,i){const n={text_styles:e.saved};return e.old.compression!==i.compression&&(n.heading_compression=C(e.old.compression)),n}const b=(e,i)=>Number(e.toFixed(i));function se(e,i){return e.map(n=>{const{lineHeight:t,letterSpacing:a,...s}=n.values,o={...s};return t!==void 0&&(o.lineSpacing=b(t*100,1)),a!==void 0&&(o.letterSpacing=b(a*i(n),2)),{id:n.id,kind:n.kind,name:n.name,values:o}})}function W(e,i){return e.map(n=>{const{lineSpacing:t,letterSpacing:a,...s}=n.values,o={id:n.id,kind:n.kind,name:n.name,values:p({...s})},l={...o.values};if(t!==void 0&&(l.lineHeight=b(t/100,3)),a!==void 0){const r=i({...o,values:{...o.values}});l.letterSpacing=r?b(a/r,4):0}return{...o,values:p(l)}})}function re(e){const i={fonts:F({heading:e.heading_font??void 0,heading2:e.heading_2_font??void 0,heading3:e.heading_3_font??void 0,body:e.body_font??void 0,accent:e.accent_font??void 0,label:e.label_font??void 0}),sizes:I({headingLarge:e.heading_size_large_px,headingMedium:e.heading_size_medium_px,headingSmall:e.heading_size_small_px,accent:e.accent_size_px,body:e.body_size_px,label:e.label_size_px}),compression:C(e.heading_compression),typography:U(e)},n=e.text_styles??null,t=Array.isArray(n)?L(T(n,i),i):i;return{fonts:F(t.fonts),sizes:I(t.sizes),compression:C(t.compression),typography:t.typography,textStyles:n}}function L(e,i){const n=d=>e.find(f=>f.id===d)?.values??{},t=n("heading-1"),a=n("heading-2"),s=n("heading-3"),o=n("body-1"),l=n("body-5"),r=i.fonts,g=i.sizes,y=i.typography,$=d=>d==="none"?"sentence":"authored";return{fonts:{...r,heading:t.font??r.heading,heading2:a.font??t.font??r.heading2,heading3:s.font??t.font??r.heading3,body:o.font??r.body,label:l.font??r.label},sizes:{...g,headingLarge:t.size??null,headingMedium:a.size??null,headingSmall:s.size??null,body:o.size??null,label:l.size??null},compression:i.compression,typography:{...y,case:$(t.letterCase),weight:t.weight??null,maxPx:t.largestSize??null,weight2:a.weight??null,weight3:s.weight??null,case2:$(a.letterCase),case3:$(s.letterCase)}}}function de(e){const i=_(e.textStyles);if(!i||i.every(a=>Object.keys(a.values).length===0&&!a.auto))return e;const n={fonts:e.fonts??{},sizes:e.sizes??{},compression:String(e.headingCompression??"normal"),typography:e.headingTypography??{}},t=L(T(i,n),n);return{...e,fonts:t.fonts,sizes:t.sizes,headingCompression:t.compression,headingTypography:t.typography}}function X(e){switch(e.id){case"heading-1":return"font-display";case"heading-2":return"font-heading2";case"heading-3":return"font-heading3";case"body-1":return"font-body";case"body-5":return"font-mono";default:return`font-${e.id}`}}const Z={none:"none",upper:"uppercase",capitalize:"capitalize",lower:"lowercase"};function q(e){return e==="accent"?"var(--accent)":e==="text"?"var(--fg)":e==="labels"?"var(--fg-dim)":/^#[0-9a-f]{6}$/i.test(e)?e:null}function K(e){const i=":is([data-sf-bullet], [data-sf-number])";return[`${e} br:not(br + br) { display: block; content: ""; margin-top: var(--sf-part-para) }`,`${e} ${i}:not(:first-child) { margin-top: var(--sf-part-para) }`,`${e} ${i}:not(:last-child) { margin-bottom: var(--sf-part-para) }`]}function le(e){const i=[];for(const n of e){const t=n.values,a=`.${X(n)}`,s=[],o=t.font!==void 0?D.find(g=>g.key===t.font)?.cssVar:void 0;if(o&&s.push(`font-family: var(${o})`),t.size!==void 0){const g=t.largestSize!==void 0?Math.min(t.size,t.largestSize):t.size;s.push(`font-size: ${g}px`)}const l=t.color!==void 0?q(t.color):null;l&&s.push(`color: ${l}`),t.italic===!0&&s.push("font-style: italic"),t.weight!==void 0&&s.push(`font-weight: ${t.weight}`);const r=[];if(t.lineHeight!==void 0&&r.push(`line-height: ${t.lineHeight}`),t.letterSpacing!==void 0&&r.push(`letter-spacing: ${t.letterSpacing}em`),n.kind==="body"&&s.push(...r),t.letterCase!==void 0&&s.push(`text-transform: ${Z[t.letterCase]}`),t.paragraphSpacing&&s.push(`--sf-part-para: ${t.paragraphSpacing}px`),s.length&&i.push(`${a} { ${s.join("; ")} }`),n.kind==="heading"&&r.length&&i.push(`${a}, ${a}:is(h1, h2, h3, h4) { ${r.join("; ")} }`),t.paragraphSpacing&&i.push(...K(a)),t.sizePhone!==void 0){const g=Q[n.id],y=[`${a} { font-size: ${t.sizePhone}px }`];g&&y.push(`.sf-themed-content { --size-${g}: ${t.sizePhone}px !important }`),i.push(`@media ${J} { ${y.join(" ")} }`)}}return i.join(`
2
+ `)}const Q={"heading-1":"headingLarge","heading-2":"headingMedium","heading-3":"headingSmall","body-1":"body","body-5":"label"};function ce(e){return e!==!0&&e!==!1?"":`em.text-accent { font-style: ${e?"italic":"normal"} }`}function ue(e){switch(e){case"heading":return"heading-1";case"heading2":return"heading-2";case"heading3":return"heading-3";case"body":return"body-1";case"label":return"body-5";default:return}}function Y(e){const i=B(e);return p({font:e.font,color:e.color,size:e.size,weight:e.weight,italic:e.italic,lineHeight:i.lineHeight,letterSpacing:i.letterSpacingEm,paragraphSpacing:e.paragraphSpacing,letterCase:e.case})}function fe(e){return Object.keys(Y(e)).length>0}function ge({saved:e,old:i,styleId:n,values:t}){return{saved:(_(e)??O(i).map(o=>({...o,values:{}}))).map(o=>o.id===n?w({...o,values:p({...o.values,...t})},o.auto):o),old:i}}function pe(e){const i=e.filter(n=>n.kind==="body").map(n=>Number(/^body-(\d+)$/.exec(n.id)?.[1])).filter(n=>Number.isInteger(n)&&n>0);return i.length?`body-${Math.max(...i)}`:"body-1"}export{ce as accentItalicCss,ae as applyListChange,pe as eyebrowStyleId,X as faceClassOf,W as fromPanelStyles,fe as hasStyleChange,oe as listChangePatch,Y as partStyleToTextStyleValues,ge as pushIntoStyle,T as resolveTextStyles,re as siteTextFromRow,ie as storedTextStyles,ue as styleIdOfRole,le as textStylesCss,O as textStylesFromOld,L as textStylesToOld,de as themeWithTextStyles,se as toPanelStyles};
@@ -1,8 +1,8 @@
1
- import{HEADING_FACE_UTILITIES as _}from"../fonts.js";import{isRuleSource as k,ruleChecks as T}from"./rule.js";import{evalLength as S}from"./support/css-length.js";const j=["app/","components/","lib/"],v="40px",E=["1","1.4"],b=/(?:^|\s)((?:[a-z0-9@_.-]*\[[^\]]*\]:|[a-z0-9@_.-]+:)*)text-\[([^\]]*)\]/g;function L(t){const e=/max-\[(\d+)px\]:/.exec(t);return e?Number(e[1]):1600}function O(t,e){let s=-1,o="";for(let i=e;i>0;i--){const l=t[i-1];if((l==="`"||l==="'"||l==='"')&&t[i-2]!=="\\"){o=l,s=i;break}}if(s===-1)return null;for(let i=e;i<t.length;i++)if(t[i]===o&&t[i-1]!=="\\")return t.slice(s,i);return null}const A=/^(#|rgb|hsl|oklch|color|currentColor|inherit|transparent)/;function y(t){const e=[];for(const s of t){const o=s.text,i=new RegExp(_.join("|"),"g");let l;for(;l=i.exec(o);){const a=O(o,l.index);if(!a)continue;const h=[];b.lastIndex=0;let c;for(;c=b.exec(a);){const p=c[2].replace(/^length:/,"");A.test(p)||h.push({token:c[0].trim(),expr:p,vw:L(c[1])})}h.length!==0&&e.push({file:s.path,line:o.slice(0,l.index).split(`
2
- `).length,className:a,steps:h})}}return e}function u(t,e){return S(t.expr,t.vw,e)}const C=t=>u(t,{"--scale-heading":"1"})>40;function m(t,e){return t[`${e.file}:${e.line}`]??Object.entries(t).find(([s])=>{const o=s.indexOf("#");return o>0&&s.slice(0,o)===e.file&&e.className.includes(s.slice(o+1))})?.[1]??t[e.file]}const I=t=>t.filter(e=>/\.tsx?$/.test(e.path)&&!k(e.path)&&j.some(s=>e.path.startsWith(s)));function D(t,e){const s=e.notSwept??{},o=y(I(t)).filter(l=>!m(s,l)).flatMap(l=>l.steps.filter(a=>!C(a)&&u(a,{"--sf-heading-max":v,"--scale-heading":"1.4"})>40).map(a=>`${l.file}:${l.line} · ${a.token}`)),i=[];if(o.length){i.push(` note — ${o.length} step(s) clear 40px at the default heading scale but not at 1.4; outside this card's census, listed so they are not a silent gap:`);for(const l of o)i.push(` ${l}`)}for(const[l,a]of Object.entries(s))i.push(` note — not swept: ${l} — ${a}`);return i}function H(t,e){const{check:s,report:o}=T("heading ceiling sweep"),i=e.notSwept??{},l=I(t),a=y(l);s(`read the source (${l.length} .ts/.tsx files)`,l.length>0,"found no source to sweep — this rule is not measuring anything"),s(`the scanner finds display headings at all (${a.length}, at least ${e.minSites})`,a.length>=e.minSites,`found ${a.length} heading-face class lists with a text-[…] size`);const h=a.filter(n=>!m(i,n)&&n.steps.some(C));for(const n of Object.keys(i)){const r=n.split(/[:#]/)[0];s(`notSwept["${n}"] still matches a real site`,a.some(f=>f.file===r&&m(i,f)===i[n]))}s(`the census finds the headings a 40px ceiling has to bring down (${h.length}, at least ${e.minSwept})`,h.length>=e.minSwept,`only ${h.length} class lists declare a size above 40px`);const c=[];for(const n of h)for(const r of n.steps)for(const f of E){const d=u(r,{"--sf-heading-max":v,"--scale-heading":f});d>40&&c.push(`${n.file}:${n.line} @scale ${f} → ${d}px · ${r.token}`)}s("no swept display heading renders above the site's 40px ceiling",c.length===0,`${c.length} step(s) ignore it:
1
+ import{HEADING_FACE_UTILITIES as _}from"../fonts.js";import{inlineTextSizes as T,isRuleSource as k,ruleChecks as j}from"./rule.js";import{evalLength as S}from"./support/css-length.js";const L=["app/","components/","lib/"],v="40px",E=["1","1.4"],b=/(?:^|\s)((?:[a-z0-9@_.-]*\[[^\]]*\]:|[a-z0-9@_.-]+:)*)text-\[([^\]]*)\]/g;function O(t){const e=/max-\[(\d+)px\]:/.exec(t);return e?Number(e[1]):1600}function z(t,e){let s=-1,o="";for(let i=e;i>0;i--){const l=t[i-1];if((l==="`"||l==="'"||l==='"')&&t[i-2]!=="\\"){o=l,s=i;break}}if(s===-1)return null;for(let i=e;i<t.length;i++)if(t[i]===o&&t[i-1]!=="\\")return t.slice(s,i);return null}const A=/^(#|rgb|hsl|oklch|color|currentColor|inherit|transparent)/;function y(t){const e=[];for(const s of t){const o=T(s.text),i=new RegExp(_.join("|"),"g");let l;for(;l=i.exec(o);){const a=z(o,l.index);if(!a)continue;const h=[];b.lastIndex=0;let c;for(;c=b.exec(a);){const p=c[2].replace(/^length:/,"");A.test(p)||h.push({token:c[0].trim(),expr:p,vw:O(c[1])})}h.length!==0&&e.push({file:s.path,line:o.slice(0,l.index).split(`
2
+ `).length,className:a,steps:h})}}return e}function u(t,e){return S(t.expr,t.vw,e)}const C=t=>u(t,{"--scale-heading":"1"})>40;function m(t,e){return t[`${e.file}:${e.line}`]??Object.entries(t).find(([s])=>{const o=s.indexOf("#");return o>0&&s.slice(0,o)===e.file&&e.className.includes(s.slice(o+1))})?.[1]??t[e.file]}const I=t=>t.filter(e=>/\.tsx?$/.test(e.path)&&!k(e.path)&&L.some(s=>e.path.startsWith(s)));function H(t,e){const s=e.notSwept??{},o=y(I(t)).filter(l=>!m(s,l)).flatMap(l=>l.steps.filter(a=>!C(a)&&u(a,{"--sf-heading-max":v,"--scale-heading":"1.4"})>40).map(a=>`${l.file}:${l.line} · ${a.token}`)),i=[];if(o.length){i.push(` note — ${o.length} step(s) clear 40px at the default heading scale but not at 1.4; outside this card's census, listed so they are not a silent gap:`);for(const l of o)i.push(` ${l}`)}for(const[l,a]of Object.entries(s))i.push(` note — not swept: ${l} — ${a}`);return i}function F(t,e){const{check:s,report:o}=j("heading ceiling sweep"),i=e.notSwept??{},l=I(t),a=y(l);s(`read the source (${l.length} .ts/.tsx files)`,l.length>0,"found no source to sweep — this rule is not measuring anything"),s(`the scanner finds display headings at all (${a.length}, at least ${e.minSites})`,a.length>=e.minSites,`found ${a.length} heading-face class lists with a text-[…] size`);const h=a.filter(n=>!m(i,n)&&n.steps.some(C));for(const n of Object.keys(i)){const r=n.split(/[:#]/)[0];s(`notSwept["${n}"] still matches a real site`,a.some(f=>f.file===r&&m(i,f)===i[n]))}s(`the census finds the headings a 40px ceiling has to bring down (${h.length}, at least ${e.minSwept})`,h.length>=e.minSwept,`only ${h.length} class lists declare a size above 40px`);const c=[];for(const n of h)for(const r of n.steps)for(const f of E){const d=u(r,{"--sf-heading-max":v,"--scale-heading":f});d>40&&c.push(`${n.file}:${n.line} @scale ${f} → ${d}px · ${r.token}`)}s("no swept display heading renders above the site's 40px ceiling",c.length===0,`${c.length} step(s) ignore it:
3
3
  ${c.join(`
4
4
  `)}`);const p=a.flatMap(n=>n.steps.filter(r=>r.expr.includes("--sf-heading-max")).map(r=>({site:n,step:r})));s(`the sweep actually capped headings (${p.length} steps read --sf-heading-max, at least ${e.minCapped})`,p.length>=e.minCapped);const g=[];for(const{site:n,step:r}of p){const f=/^min\((.*),\s*var\(--sf-heading-max,[^)]*\)\)$/.exec(r.expr)?.[1];if(f!==void 0)for(const d of E){const $=S(f,r.vw,{"--scale-heading":d}),w=u(r,{"--scale-heading":d});$!==w&&g.push(`${n.file}:${n.line} @scale ${d}: ${$}px → ${w}px`)}}if(s("a tenant that sets no ceiling renders exactly what it renders today",g.length===0,`${g.length} step(s) moved:
5
5
  ${g.join(`
6
6
  `)}`),e.reference!==void 0){const n=t.find(r=>r.path===e.reference);s(`the reference component still spells the ceiling the one way (${e.reference})`,!!n&&/var\(--sf-heading-max,\s*9999px\)/.test(n.text),`${e.reference} no longer reads var(--sf-heading-max,9999px)`)}const N=/^var\(--sf-heading-max,/,x=p.filter(({step:n})=>!N.test(n.expr)).filter(({step:n})=>!/^min\(.*,var\(--sf-heading-max,9999px\)\)$/.test(n.expr)).map(({site:n,step:r})=>`${n.file}:${n.line} · ${r.token}`);return s("every capped step is spelt the reference's way",x.length===0,`${x.length} second spelling(s):
7
7
  ${x.join(`
8
- `)}`),o()}export{O as enclosingLiteral,D as headingCeilingNotes,H as headingCeilingSweep};
8
+ `)}`),o()}export{z as enclosingLiteral,H as headingCeilingNotes,F as headingCeilingSweep};
@@ -0,0 +1,5 @@
1
+ import { type RuleReport, type SourceFile } from "./rule.js";
2
+ export type OneMarkupGrammarOptions = {
3
+ readonly minFiles: number;
4
+ };
5
+ export declare function oneMarkupGrammar(files: readonly SourceFile[], options: OneMarkupGrammarOptions): RuleReport;
@@ -0,0 +1,4 @@
1
+ import{ruleChecks as h}from"./rule.js";const r=o=>JSON.stringify(o),u=/\.(ts|tsx|js|jsx|mjs|cjs)$/,k=/(?:from\s*|import\s*\(\s*|require\s*\(\s*|import\s+)["']([^"']+)["']/g,t=["efficient-easel","markup"].join("/"),p=o=>[...o.matchAll(k)].some(s=>s[1]===t);function d(o,s){const{check:a,report:c}=h("one markup grammar");{const e=[["a named import",`import { parseMarkup } from ${r(t)};`,!0],["a type import",`import type { MarkStyle } from ${r(t)};`,!0],["a re-export",`export { applyMark } from ${r(t)};`,!0],["a dynamic import()",`const m = await import(${r(t)});`,!0],["a require()",`const m = require(${r(t)});`,!0],["the shell's grammar",`import { parseMarkup } from ${r(["efficient-shell","markup"].join("/"))};`,!1],["another easel subpath",`import { CopyBox } from ${r(["efficient-easel","copy"].join("/"))};`,!1]];for(const[l,f,n]of e)a(`the reader ${n?"finds":"leaves alone"} ${l}`,p(f)===n)}const i=o.filter(e=>u.test(e.path));a(`read the source (${i.length} JS/TS files, at least ${s.minFiles})`,i.length>=s.minFiles,"fewer means the walk broke, and every check below is vacuous");const m=i.filter(e=>p(e.text)).map(e=>e.path);return a(`no file imports ${t}`,m.length===0,`the retired copy of the grammar is imported:
2
+ ${m.join(`
3
+ `)}
4
+ Read the one grammar from efficient-shell/markup instead (decision D1, 2026-10-12).`),c()}export{d as oneMarkupGrammar};
@@ -0,0 +1,6 @@
1
+ import { type RuleReport, type SourceFile } from "./rule.js";
2
+ export type OneSiteColumnOptions = {
3
+ readonly home?: string;
4
+ readonly minFiles: number;
5
+ };
6
+ export declare function oneSiteColumn(files: readonly SourceFile[], options: OneSiteColumnOptions): RuleReport;
@@ -0,0 +1,3 @@
1
+ import{SITE_COLUMN as h}from"../content/page-width.js";import{ruleChecks as a}from"./rule.js";function n(r){return r.includes(h)}function u(r,t){const{check:o,report:l}=a("one site column");o("the reader finds a copy, and passes an import",n(`const SITE_COLUMN =
2
+ "${h}";`)&&!n(`import { SITE_COLUMN } from "x";
3
+ <div className={SITE_COLUMN} />`));const s=r.filter(e=>/\.tsx?$/.test(e.path));o(`read the source (${s.length} TS/TSX files, at least ${t.minFiles})`,s.length>=t.minFiles,"fewer means the walk broke, and the check below is vacuous");const i=s.filter(e=>e.path!==t.home&&n(e.text)).map(e=>e.path);return o("no file spells the site column out; each imports SITE_COLUMN",i.length===0,`a copy in: ${i.join(", ")}`),t.home&&o(`${t.home} defines it`,s.some(e=>e.path===t.home&&n(e.text)),"the home file has no SITE_COLUMN"),l()}export{u as oneSiteColumn};
@@ -1,2 +1,2 @@
1
- import{fontRoleOfClassName as y,FONT_ROLE_LABELS as x}from"../fonts.js";import{isRuleSource as w,ruleChecks as E}from"./rule.js";import{enclosingLiteral as N}from"./heading-ceiling-sweep.js";import{stripComments as b}from"./support/ts-source.js";import{typeScaleGroupOfClassName as u}from"../theme.js";const R=["app/","components/","lib/"],p={"font-display":"heading","font-heading2":"heading2","font-heading3":"heading3","font-body":"body","font-serif":"accent","font-mono":"label"},k={heading:"--font-role-heading",heading2:"--font-role-heading2",heading3:"--font-role-heading3",body:"--font-role-body",accent:"--font-role-accent",label:"--font-role-label"},$=[];function L(m){const n=[],t=new Set;for(const d of m){const c=b(d.text),l=/\bfont-(?:display|body|serif|mono)\b/g;let r;for(;r=l.exec(c);){const i=N(c,r.index);if(!i)continue;const o=c.slice(0,r.index).split(`
2
- `).length,h=`${d.path}:${o}:${i}`;if(t.has(h))continue;t.add(h);const g=i.split(/\s+/).filter(a=>!a.slice(0,a.lastIndexOf(":")+1).includes("&")).map(a=>a.slice(a.lastIndexOf(":")+1)),f=Object.keys(p).filter(a=>g.includes(a));f.length!==0&&n.push({file:d.path,line:o,className:i,utilities:f})}}return n}function I(m,n){const{check:t,report:d}=E("part style naming"),c=m.filter(e=>/\.tsx?$/.test(e.path)&&!w(e.path)&&R.some(s=>e.path.startsWith(s))),l=L(c),r=new Map(m.map(e=>[e.path,e]));t(`read the source (${c.length} .ts/.tsx files)`,c.length>0,"found no source to sweep — this rule is not measuring anything"),t(`the scanner finds face-tagged class lists at all (${l.length}, at least ${n.minSites})`,l.length>=n.minSites);const i=l.filter(e=>u(e.className)!==null),o=l.filter(e=>u(e.className)===null);t(`…both taggings are represented, so neither branch below is untested (${i.length} with a scale token, at least ${n.minWithScale})`,i.length>=n.minWithScale&&i.length>0),t(`…and the face-without-a-size-knob population is real, not an artefact (${o.length}, at least ${n.minWithoutScale})`,o.length>=n.minWithoutScale&&o.length>0,`${o.length} class lists carry a face utility and no matching scale token`);const h=l.filter(e=>y(e.className)!==p[e.utilities[0]]);if(t("every class list in the census reads back as its own utility's role",h.length===0,h.slice(0,5).map(e=>`${e.file}:${e.line} has ${e.utilities[0]} but reads ${y(e.className)}`).join("; ")),n.roleSheet!==void 0){const e=r.get(n.roleSheet),s=Object.values(k).filter(O=>!e||!e.text.includes(O));t(`…and every role named here is a custom property ${n.roleSheet} defines`,!!e&&s.length===0,e?s.join(", "):"the sheet was not read")}const g=Object.values(p).filter(e=>!(e in x));t("every face utility names a role the panel offers",g.length===0,g.join(", "));const f=Object.keys(x).filter(e=>!Object.values(p).includes(e));t("…and the roles no utility can name are exactly the ones decided to have none",f.join()===$.join(),`no utility reaches: [${f}] — expected [${$}]`);const a=o.filter(e=>u(e.className)!==null);t("no class list without a scale token claims a size group",a.length===0,a.slice(0,5).map(e=>`${e.file}:${e.line}`).join("; ")),t("the reported case: a `font-display text-[22px]` heading is Headings, size group none",y("font-display text-[22px] font-medium")==="heading"&&u("font-display text-[22px] font-medium")===null),t("…and genuinely body-tagged copy still reads as the body size group",u("text-[var(--size-body,16px)]")==="body");const S=l.filter(e=>e.utilities.length>1);if(t("every class list names at most one face",S.length===0,S.slice(0,5).map(e=>`${e.file}:${e.line} — ${e.utilities.join(" + ")}`).join("; ")),n.panel!==void 0){const e=r.get(n.panel),s=e?b(e.text):"";t(`read the part panel ${n.panel}`,!!e),t("the box's name comes from the FACE, not from a size group",s.includes("FONT_ROLE_LABELS[face.role]")&&!s.includes("TYPE_GROUP_LABELS[")),t("…a face the page does not draw as its declared role is called Custom",/return\s*"Custom"/.test(s)&&s.includes('kind: "custom"')),t("…and a part no size knob reaches says so under the text box",/sizeGroup === null && \(\s*<p className="es-field-hint sf-hint-keep">(?:\{say\(")?Fixed size/.test(s))}if(n.bridge!==void 0){const e=r.get(n.bridge),s=e?b(e.text):"";t(`read the preview's part bridge ${n.bridge}`,!!e),t("the preview publishes BOTH tags, each walked up to its own nearest declarer",s.includes("fontRoleOfClassName")&&s.includes("typeScaleGroupOfClassName")&&s.includes("el.parentElement")),t("…and the element it reads from is one that declares EITHER tag",/partStyleSource\([\s\S]{0,200}fontRoleOfClassName[\s\S]{0,120}typeScaleGroupOfClassName/.test(s))}return d()}export{I as partStyleNaming};
1
+ import{fontRoleOfClassName as y,FONT_ROLE_LABELS as x}from"../fonts.js";import{inlineTextSizes as w,isRuleSource as E,ruleChecks as N}from"./rule.js";import{enclosingLiteral as R}from"./heading-ceiling-sweep.js";import{stripComments as b}from"./support/ts-source.js";import{typeScaleGroupOfClassName as u}from"../theme.js";const T=["app/","components/","lib/"],p={"font-display":"heading","font-heading2":"heading2","font-heading3":"heading3","font-body":"body","font-serif":"accent","font-mono":"label"},k={heading:"--font-role-heading",heading2:"--font-role-heading2",heading3:"--font-role-heading3",body:"--font-role-body",accent:"--font-role-accent",label:"--font-role-label"},$=[];function L(m){const n=[],t=new Set;for(const d of m){const c=b(w(d.text)),l=/\bfont-(?:display|body|serif|mono)\b/g;let r;for(;r=l.exec(c);){const i=R(c,r.index);if(!i)continue;const o=c.slice(0,r.index).split(`
2
+ `).length,h=`${d.path}:${o}:${i}`;if(t.has(h))continue;t.add(h);const g=i.split(/\s+/).filter(a=>!a.slice(0,a.lastIndexOf(":")+1).includes("&")).map(a=>a.slice(a.lastIndexOf(":")+1)),f=Object.keys(p).filter(a=>g.includes(a));f.length!==0&&n.push({file:d.path,line:o,className:i,utilities:f})}}return n}function I(m,n){const{check:t,report:d}=N("part style naming"),c=m.filter(e=>/\.tsx?$/.test(e.path)&&!E(e.path)&&T.some(s=>e.path.startsWith(s))),l=L(c),r=new Map(m.map(e=>[e.path,e]));t(`read the source (${c.length} .ts/.tsx files)`,c.length>0,"found no source to sweep — this rule is not measuring anything"),t(`the scanner finds face-tagged class lists at all (${l.length}, at least ${n.minSites})`,l.length>=n.minSites);const i=l.filter(e=>u(e.className)!==null),o=l.filter(e=>u(e.className)===null);t(`…both taggings are represented, so neither branch below is untested (${i.length} with a scale token, at least ${n.minWithScale})`,i.length>=n.minWithScale&&i.length>0),t(`…and the face-without-a-size-knob population is real, not an artefact (${o.length}, at least ${n.minWithoutScale})`,o.length>=n.minWithoutScale&&o.length>0,`${o.length} class lists carry a face utility and no matching scale token`);const h=l.filter(e=>y(e.className)!==p[e.utilities[0]]);if(t("every class list in the census reads back as its own utility's role",h.length===0,h.slice(0,5).map(e=>`${e.file}:${e.line} has ${e.utilities[0]} but reads ${y(e.className)}`).join("; ")),n.roleSheet!==void 0){const e=r.get(n.roleSheet),s=Object.values(k).filter(O=>!e||!e.text.includes(O));t(`…and every role named here is a custom property ${n.roleSheet} defines`,!!e&&s.length===0,e?s.join(", "):"the sheet was not read")}const g=Object.values(p).filter(e=>!(e in x));t("every face utility names a role the panel offers",g.length===0,g.join(", "));const f=Object.keys(x).filter(e=>!Object.values(p).includes(e));t("…and the roles no utility can name are exactly the ones decided to have none",f.join()===$.join(),`no utility reaches: [${f}] — expected [${$}]`);const a=o.filter(e=>u(e.className)!==null);t("no class list without a scale token claims a size group",a.length===0,a.slice(0,5).map(e=>`${e.file}:${e.line}`).join("; ")),t("the reported case: a `font-display text-[22px]` heading is Headings, size group none",y("font-display text-[22px] font-medium")==="heading"&&u("font-display text-[22px] font-medium")===null),t("…and genuinely body-tagged copy still reads as the body size group",u("text-[var(--size-body,16px)]")==="body");const S=l.filter(e=>e.utilities.length>1);if(t("every class list names at most one face",S.length===0,S.slice(0,5).map(e=>`${e.file}:${e.line} — ${e.utilities.join(" + ")}`).join("; ")),n.panel!==void 0){const e=r.get(n.panel),s=e?b(e.text):"";t(`read the part panel ${n.panel}`,!!e),t("the box's name comes from the FACE, not from a size group",s.includes("FONT_ROLE_LABELS[face.role]")&&!s.includes("TYPE_GROUP_LABELS[")),t("…a face the page does not draw as its declared role is called Custom",/return\s*"Custom"/.test(s)&&s.includes('kind: "custom"')),t("…and a part no size knob reaches says so under the text box",/sizeGroup === null && \(\s*<p className="es-field-hint sf-hint-keep">(?:\{say\(")?Fixed size/.test(s))}if(n.bridge!==void 0){const e=r.get(n.bridge),s=e?b(e.text):"";t(`read the preview's part bridge ${n.bridge}`,!!e),t("the preview publishes BOTH tags, each walked up to its own nearest declarer",s.includes("fontRoleOfClassName")&&s.includes("typeScaleGroupOfClassName")&&s.includes("el.parentElement")),t("…and the element it reads from is one that declares EITHER tag",/partStyleSource\([\s\S]{0,200}fontRoleOfClassName[\s\S]{0,120}typeScaleGroupOfClassName/.test(s))}return d()}export{I as partStyleNaming};
@@ -2,6 +2,7 @@ export type SourceFile = {
2
2
  readonly path: string;
3
3
  readonly text: string;
4
4
  };
5
+ export declare function inlineTextSizes(text: string): string;
5
6
  export type RuleCheck = {
6
7
  readonly name: string;
7
8
  readonly ok: boolean;
@@ -1 +1,3 @@
1
- const u="lib/source-rules/";function c(r){return r.startsWith(u)}function s(r){const e=[];return{check(n,t,o){return e.push(t||o===void 0?{name:n,ok:t}:{name:n,ok:t,detail:o}),t},report(){return e.length===0&&e.push({name:`${r} checked nothing`,ok:!1}),{rule:r,checks:e}}}}function i(r){return r.checks.filter(e=>!e.ok)}function h(r){return r.checks.map(e=>e.ok?` ok ${e.name}`:` FAIL ${e.name}${e.detail?` — ${e.detail}`:""}`)}export{u as RULES_DIR,i as failedChecks,c as isRuleSource,h as reportLines,s as ruleChecks};
1
+ function u(n){const e=/\nconst TEXT_SIZE = \{\n([\s\S]*?)\} as const;\n/.exec(n);if(!e)return n;const r=new Map;for(const s of e[1].matchAll(/^\s*(\w+): "([^"]+)",\s*$/gm))r.set(s[1],s[2]);const t=`
2
+ `.repeat(e[0].split(`
3
+ `).length-1);return(n.slice(0,e.index)+t+n.slice(e.index+e[0].length)).replace(/\$\{TEXT_SIZE\.(\w+)\}/g,(s,c)=>r.get(c)??s)}const i="lib/source-rules/";function l(n){return n.startsWith(i)}function h(n){const e=[];return{check(r,t,o){return e.push(t||o===void 0?{name:r,ok:t}:{name:r,ok:t,detail:o}),t},report(){return e.length===0&&e.push({name:`${n} checked nothing`,ok:!1}),{rule:n,checks:e}}}}function p(n){return n.checks.filter(e=>!e.ok)}function a(n){return n.checks.map(e=>e.ok?` ok ${e.name}`:` FAIL ${e.name}${e.detail?` — ${e.detail}`:""}`)}export{i as RULES_DIR,p as failedChecks,u as inlineTextSizes,l as isRuleSource,a as reportLines,h as ruleChecks};
@@ -1,11 +1,11 @@
1
- import{ruleChecks as d}from"./rule.js";const b=/text-\[(?!length:)[^\]]*--size-[a-zA-Z]+[^\]]*\]/g;function w(t){let i="",e=0;const l=t.length;for(;e<l;){const a=t[e],h=t[e+1];if(a==="/"&&h==="/"){for(;e<l&&t[e]!==`
1
+ import{inlineTextSizes as d,ruleChecks as b}from"./rule.js";const w=/text-\[(?!length:)[^\]]*--size-[a-zA-Z]+[^\]]*\]/g;function x(t){let i="",e=0;const l=t.length;for(;e<l;){const a=t[e],h=t[e+1];if(a==="/"&&h==="/"){for(;e<l&&t[e]!==`
2
2
  `;)i+=" ",e++;continue}if(a==="/"&&h==="*"){for(;e<l&&!(t[e]==="*"&&t[e+1]==="/");)i+=t[e]===`
3
3
  `?`
4
4
  `:" ",e++;i+=" ",e+=2;continue}if(a==="`"){for(i+=a,e++;e<l;){if(t[e]==="\\"){i+=t[e]+(t[e+1]??""),e+=2;continue}if(t[e]==="`"){i+="`",e++;break}i+=t[e],e++}continue}if(a==='"'||a==="'"){let o=e+1,r=!1;for(;o<l&&t[o]!==`
5
- `;){if(t[o]==="\\"){o+=2;continue}if(t[o]===a){r=!0;break}o++}if(!r){i+=a,e++;continue}i+=t.slice(e,o+1),e=o+1;continue}i+=a,e++}return i}function f(t){return(w(t).match(b)??[]).length}function x(){const t="text-[";return[{name:"empty source",src:"",want:0},{name:"no size token at all",src:`<p className="${t}13px] font-mono">hi</p>`,want:0},{name:"the broken form",src:`<p className="${t}var(--size-label,13px)]">hi</p>`,want:1},{name:"the working form",src:`<p className="${t}length:var(--size-label,13px)]">hi</p>`,want:0},{name:"two on one line, desktop and phone",src:`<h2 className="${t}var(--size-headingMedium,44px)] max-[800px]:${t}var(--size-headingMedium,32px)]">t</h2>`,want:2},{name:"wrapped in min(), still broken",src:`<h1 className="${t}min(var(--size-headingLarge,64px),var(--sf-heading-max,9999px))]">t</h1>`,want:1},{name:"a line comment is not code",src:`// ${t}var(--size-label,13px)] is the broken form`,want:0},{name:"a block comment is not code",src:`/* ${t}var(--size-label,13px)] */`,want:0},{name:"an apostrophe does not stop a later comment being stripped",src:`<p>it doesn't paint</p>
5
+ `;){if(t[o]==="\\"){o+=2;continue}if(t[o]===a){r=!0;break}o++}if(!r){i+=a,e++;continue}i+=t.slice(e,o+1),e=o+1;continue}i+=a,e++}return i}function f(t){return(x(t).match(w)??[]).length}function g(){const t="text-[";return[{name:"empty source",src:"",want:0},{name:"no size token at all",src:`<p className="${t}13px] font-mono">hi</p>`,want:0},{name:"the broken form",src:`<p className="${t}var(--size-label,13px)]">hi</p>`,want:1},{name:"the working form",src:`<p className="${t}length:var(--size-label,13px)]">hi</p>`,want:0},{name:"two on one line, desktop and phone",src:`<h2 className="${t}var(--size-headingMedium,44px)] max-[800px]:${t}var(--size-headingMedium,32px)]">t</h2>`,want:2},{name:"wrapped in min(), still broken",src:`<h1 className="${t}min(var(--size-headingLarge,64px),var(--sf-heading-max,9999px))]">t</h1>`,want:1},{name:"a line comment is not code",src:`// ${t}var(--size-label,13px)] is the broken form`,want:0},{name:"a block comment is not code",src:`/* ${t}var(--size-label,13px)] */`,want:0},{name:"an apostrophe does not stop a later comment being stripped",src:`<p>it doesn't paint</p>
6
6
  // ${t}var(--size-label,13px)]
7
- <p>that's why</p>`,want:0}]}function $(t,i){const{check:e,report:l}=d("size token paints"),a=i.baseline??{};let h=!0;for(const n of x()){const s=f(n.src);h=e(`fixture "${n.name}": ${n.want}`,s===n.want,`got ${s}`)&&h}if(!h)return e("the extractor is right, so its verdict on the tree means something",!1,"a fixture failed above"),l();const o=t.filter(n=>/\.tsx?$/.test(n.path));e(`read the source (${o.length} TS/TSX files, at least ${i.minFiles})`,o.length>=i.minFiles,"fewer means the walk broke, and the check below is vacuous");const r=[],p=[];let m=0;for(const n of o){const s=f(n.text);m+=s;const c=a[n.path]??0;s>c?r.push(`${n.path} — ${s} broken, baseline allows ${c}`):s<c&&p.push(`${n.path} — ${s} broken, baseline says ${c}`)}const u=Object.values(a).reduce((n,s)=>n+s,0);e(`no text size is written in the form that compiles to \`color:\` (${m} broken place(s) found, the baseline allows ${u})`,r.length===0,`${r.length} file(s) over budget:
7
+ <p>that's why</p>`,want:0}]}function k(t,i){const{check:e,report:l}=b("size token paints"),a=i.baseline??{};let h=!0;for(const n of g()){const s=f(n.src);h=e(`fixture "${n.name}": ${n.want}`,s===n.want,`got ${s}`)&&h}if(!h)return e("the extractor is right, so its verdict on the tree means something",!1,"a fixture failed above"),l();const o=t.filter(n=>/\.tsx?$/.test(n.path));e(`read the source (${o.length} TS/TSX files, at least ${i.minFiles})`,o.length>=i.minFiles,"fewer means the walk broke, and the check below is vacuous");const r=[],p=[];let m=0;for(const n of o){const s=f(d(n.text));m+=s;const c=a[n.path]??0;s>c?r.push(`${n.path} — ${s} broken, baseline allows ${c}`):s<c&&p.push(`${n.path} — ${s} broken, baseline says ${c}`)}const u=Object.values(a).reduce((n,s)=>n+s,0);e(`no text size is written in the form that compiles to \`color:\` (${m} broken place(s) found, the baseline allows ${u})`,r.length===0,`${r.length} file(s) over budget:
8
8
  ${r.join(`
9
9
  `)}
10
10
  Write it as \`text-[length:var(--size-<role>,<built-in>)]\`. Without \`length:\` Tailwind reads the var() as a
11
- colour, the declaration is dropped as invalid, and the site's text style setting reaches nothing.`);for(const[n]of Object.entries(a))e(`the baseline entry is still a file: ${n}`,o.some(s=>s.path===n),"delete the stale entry");for(const n of p)e(`improved — lower its baseline entry: ${n}`,!0);return l()}export{f as brokenCount,$ as sizeTokenPaints,w as stripComments};
11
+ colour, the declaration is dropped as invalid, and the site's text style setting reaches nothing.`);for(const[n]of Object.entries(a))e(`the baseline entry is still a file: ${n}`,o.some(s=>s.path===n),"delete the stale entry");for(const n of p)e(`improved — lower its baseline entry: ${n}`,!0);return l()}export{f as brokenCount,k as sizeTokenPaints,x as stripComments};
@@ -1,4 +1,4 @@
1
- import{ruleChecks as m}from"./rule.js";const u={"font-display":"headingLarge","font-heading2":"headingMedium","font-heading3":"headingSmall","font-serif":"accent","font-body":"body","font-mono":"label"};function p(i){const n=/(?<![-\w:])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)text-\[length:[^"'`]*?--size-(headingLarge|headingMedium|headingSmall|accent|body|label)\b/g,t=new Set;for(const o of i.matchAll(n))o[1]===""&&t.add(o[2]);return t.size===1?[...t][0]:null}function $(i){const n=/(?<![-\w])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)(font-display|font-heading2|font-heading3|font-serif|font-body|font-mono)(?![-\w])/g,t=new Set;for(const o of i.matchAll(n))o[1].includes("&")||t.add(o[2]);return[...t]}function f(i){const n=p(i);if(!n)return null;const t=$(i);return t.length===1?{step:n,face:t[0]}:null}const w=i=>i.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");function S(i,n){const{check:t,report:o}=m("style face matches size"),d=e=>i.find(s=>s.path===e)?.text;{const e=r=>`text-[length:var(--size-${r},44px)]`,s=r=>JSON.stringify(f(r));t("a face and a base size are read as a pair",s(`"font-display ${e("headingMedium")}"`)==='{"step":"headingMedium","face":"font-display"}'),t("…a size inside a variant is not the base",f(`"font-heading2 max-[800px]:${e("headingSmall")}"`)===null),t("…the base wins over a variant on the same line",s(`"font-heading3 ${e("headingSmall")} md:${e("headingMedium")}"`)==='{"step":"headingSmall","face":"font-heading3"}'),t("…a face behind a variant naming `&` is a descendant's, not this element's",f(`"group-[&_em]:font-serif ${e("headingLarge")}"`)===null),t("…while a bare `[&_em]:` face is still read as this element's (the reader as it shipped)",s(`"[&_em]:font-serif ${e("accent")}"`)==='{"step":"accent","face":"font-serif"}'),t("…and two faces on one line are left to a reader",f(`"font-display font-mono ${e("label")}"`)===null)}const l=i.filter(e=>/\.tsx?$/.test(e.path)&&n.drawRoots.some(s=>e.path.startsWith(s)));t(`read the drawing roots ${n.drawRoots.join(", ")} (${l.length} TS/TSX files, at least ${n.minFiles})`,l.length>=n.minFiles,"fewer means the walk broke, and the sweep below is vacuous");let h=0;const g=[];for(const e of l)e.text.split(`
2
- `).forEach((s,r)=>{const a=f(s);if(!a||(h+=1,a.step==="body"))return;const c=u[a.face];c!==a.step&&g.push(`${e.path}:${r+1} — size says ${a.step}, face says ${a.face} (${c})`)});if(t(`found >= ${n.pairFloor} lines naming one face and one size (found ${h})`,h>=n.pairFloor,"fewer means the reader broke"),t("§3 no text takes its face from one style row and its size from another",g.length===0,`these lines name one style for their size and another for their face, so the merchant cannot fix the text from one row:
1
+ import{inlineTextSizes as m,ruleChecks as u}from"./rule.js";const p={"font-display":"headingLarge","font-heading2":"headingMedium","font-heading3":"headingSmall","font-serif":"accent","font-body":"body","font-mono":"label"};function $(i){const n=/(?<![-\w:])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)text-\[length:[^"'`]*?--size-(headingLarge|headingMedium|headingSmall|accent|body|label)\b/g,t=new Set;for(const o of i.matchAll(n))o[1]===""&&t.add(o[2]);return t.size===1?[...t][0]:null}function w(i){const n=/(?<![-\w])((?:[a-z0-9-]+(?:-\[[^\]]*\])?:)*)(font-display|font-heading2|font-heading3|font-serif|font-body|font-mono)(?![-\w])/g,t=new Set;for(const o of i.matchAll(n))o[1].includes("&")||t.add(o[2]);return[...t]}function f(i){const n=$(i);if(!n)return null;const t=w(i);return t.length===1?{step:n,face:t[0]}:null}const x=i=>i.replace(/\/\*[\s\S]*?\*\//g,"").replace(/^\s*\/\/.*$/gm,"");function z(i,n){const{check:t,report:o}=u("style face matches size"),d=e=>i.find(s=>s.path===e)?.text;{const e=r=>`text-[length:var(--size-${r},44px)]`,s=r=>JSON.stringify(f(r));t("a face and a base size are read as a pair",s(`"font-display ${e("headingMedium")}"`)==='{"step":"headingMedium","face":"font-display"}'),t("…a size inside a variant is not the base",f(`"font-heading2 max-[800px]:${e("headingSmall")}"`)===null),t("…the base wins over a variant on the same line",s(`"font-heading3 ${e("headingSmall")} md:${e("headingMedium")}"`)==='{"step":"headingSmall","face":"font-heading3"}'),t("…a face behind a variant naming `&` is a descendant's, not this element's",f(`"group-[&_em]:font-serif ${e("headingLarge")}"`)===null),t("…while a bare `[&_em]:` face is still read as this element's (the reader as it shipped)",s(`"[&_em]:font-serif ${e("accent")}"`)==='{"step":"accent","face":"font-serif"}'),t("…and two faces on one line are left to a reader",f(`"font-display font-mono ${e("label")}"`)===null)}const l=i.filter(e=>/\.tsx?$/.test(e.path)&&n.drawRoots.some(s=>e.path.startsWith(s)));t(`read the drawing roots ${n.drawRoots.join(", ")} (${l.length} TS/TSX files, at least ${n.minFiles})`,l.length>=n.minFiles,"fewer means the walk broke, and the sweep below is vacuous");let h=0;const g=[];for(const e of l)m(e.text).split(`
2
+ `).forEach((s,r)=>{const a=f(s);if(!a||(h+=1,a.step==="body"))return;const c=p[a.face];c!==a.step&&g.push(`${e.path}:${r+1} — size says ${a.step}, face says ${a.face} (${c})`)});if(t(`found >= ${n.pairFloor} lines naming one face and one size (found ${h})`,h>=n.pairFloor,"fewer means the reader broke"),t("§3 no text takes its face from one style row and its size from another",g.length===0,`these lines name one style for their size and another for their face, so the merchant cannot fix the text from one row:
3
3
  `+g.join(`
4
- `)),n.themeSheet){const e=d(n.themeSheet)??"";if(t(`read the site stylesheet ${n.themeSheet}`,e.length>0)){for(const a of["2","3"])t(`§1 --font-heading${a} exists and resolves --font-role-heading${a}`,new RegExp(`--font-heading${a}:\\s*var\\(--font-role-heading${a}\\)`).test(e),`without it a section cannot DRAW the face Site styles ▸ Heading ${a} sets, and that Font control moves zero pixels again`);const s=e.slice(e.indexOf("@theme inline"));for(const a of["2","3"]){const c=s.match(new RegExp(`--font-heading${a}:[^;]*;`))?.[0]??"";t(`§2 …and in @theme inline --font-heading${a} points at its OWN role, never back at Heading 1`,c.includes(`--font-role-heading${a}`),`\`--font-heading${a}\` resolves ${c.trim()||"nothing"}`)}t("§4 the Accent size is read, in CSS (`em.text-accent` sets var(--size-accent, inherit))",/em\.text-accent\s*\{[^}]*font-size:\s*var\(--size-accent,\s*inherit\)/.test(e.replace(/\/\*[\s\S]*?\*\//g,"")),"the `em.text-accent` rule is gone, so Site styles ▸ Accent ▸ Size is a dead control again"),t("§4 …and it falls back to `inherit`, never a number",!/--size-accent,\s*\d/.test(e));const r=e.slice(e.indexOf(":root"),e.indexOf("@theme inline"));for(const a of["2","3"])t(`§5 :root defaults --font-role-heading${a} to Heading 1's face, so the swap is invisible until a face is chosen`,new RegExp(`--font-role-heading${a}:\\s*var\\(--font-role-heading\\)`).test(r),"every site that has never opened that row would change typeface")}}for(const e of n.accentMarkup??[]){const s=d(e);t(`§4 ${e} does not set the accent size in the MARKUP`,s!==void 0&&!w(s).includes("--size-accent"),s===void 0?"it is not among the files handed in":"that moves 900 published strings for a size `em.text-accent` already delivers")}if(n.fontStore){const e=d(n.fontStore)??"",s=e.slice(e.indexOf("const pick ="),e.indexOf("// DERIVED from FONT_ROLES"));t(`§5 ${n.fontStore}'s normalizeFonts falls Heading 2 and Heading 3 back to Heading 1`,/role === "heading2" \|\| role === "heading3"/.test(s),"a site that chose Oswald for its headings would get two Space Grotesk ones")}return o()}export{S as styleFaceMatchesSize};
4
+ `)),n.themeSheet){const e=d(n.themeSheet)??"";if(t(`read the site stylesheet ${n.themeSheet}`,e.length>0)){for(const a of["2","3"])t(`§1 --font-heading${a} exists and resolves --font-role-heading${a}`,new RegExp(`--font-heading${a}:\\s*var\\(--font-role-heading${a}\\)`).test(e),`without it a section cannot DRAW the face Site styles ▸ Heading ${a} sets, and that Font control moves zero pixels again`);const s=e.slice(e.indexOf("@theme inline"));for(const a of["2","3"]){const c=s.match(new RegExp(`--font-heading${a}:[^;]*;`))?.[0]??"";t(`§2 …and in @theme inline --font-heading${a} points at its OWN role, never back at Heading 1`,c.includes(`--font-role-heading${a}`),`\`--font-heading${a}\` resolves ${c.trim()||"nothing"}`)}t("§4 the Accent size is read, in CSS (`em.text-accent` sets var(--size-accent, inherit))",/em\.text-accent\s*\{[^}]*font-size:\s*var\(--size-accent,\s*inherit\)/.test(e.replace(/\/\*[\s\S]*?\*\//g,"")),"the `em.text-accent` rule is gone, so Site styles ▸ Accent ▸ Size is a dead control again"),t("§4 …and it falls back to `inherit`, never a number",!/--size-accent,\s*\d/.test(e));const r=e.slice(e.indexOf(":root"),e.indexOf("@theme inline"));for(const a of["2","3"])t(`§5 :root defaults --font-role-heading${a} to Heading 1's face, so the swap is invisible until a face is chosen`,new RegExp(`--font-role-heading${a}:\\s*var\\(--font-role-heading\\)`).test(r),"every site that has never opened that row would change typeface")}}for(const e of n.accentMarkup??[]){const s=d(e);t(`§4 ${e} does not set the accent size in the MARKUP`,s!==void 0&&!x(s).includes("--size-accent"),s===void 0?"it is not among the files handed in":"that moves 900 published strings for a size `em.text-accent` already delivers")}if(n.fontStore){const e=d(n.fontStore)??"",s=e.slice(e.indexOf("const pick ="),e.indexOf("// DERIVED from FONT_ROLES"));t(`§5 ${n.fontStore}'s normalizeFonts falls Heading 2 and Heading 3 back to Heading 1`,/role === "heading2" \|\| role === "heading3"/.test(s),"a site that chose Oswald for its headings would get two Space Grotesk ones")}return o()}export{z as styleFaceMatchesSize};
@@ -116,6 +116,11 @@ export interface ChatSettings {
116
116
  title: string;
117
117
  subtitle: string;
118
118
  greeting: string;
119
+ launcherColor?: string;
120
+ launcherIcon?: string;
121
+ launcherPosition?: "right" | "left";
122
+ launcherLabel?: string;
123
+ showOnPhones?: boolean;
119
124
  }
120
125
  export interface WebsiteChat {
121
126
  get(scope?: SiteScope): Promise<ChatSettings>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "efficient-web-builder",
3
- "version": "19.1.0",
3
+ "version": "22.0.0",
4
4
  "description": "The EFFICIENT website builder: the website editor, the general sections and the renderer that draws them, compiled. Hostable by any EFFICIENT product; nothing here imports a host.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Efficient Pty Ltd",
@@ -40,8 +40,8 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@tabler/icons-react": "^3.46.0",
43
- "efficient-easel": "^11.2.0",
44
- "efficient-shell": "^7.50.0",
43
+ "efficient-easel": "^12.0.0",
44
+ "efficient-shell": "^7.69.1",
45
45
  "isomorphic-dompurify": "^3.18.0",
46
46
  "next": ">=16",
47
47
  "react": ">=19",
@@ -55,15 +55,17 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@tabler/icons-react": "^3.46.0",
58
+ "@tanstack/react-query": "^5.100.9",
58
59
  "@types/google.maps": "^3.64.1",
59
60
  "@types/node": "^20",
60
61
  "@types/react": "^19",
61
62
  "@types/react-dom": "^19",
62
- "efficient-easel": "^11.2.0",
63
- "efficient-shell": "^7.50.0",
63
+ "efficient-easel": "^12.0.0",
64
+ "efficient-shell": "^7.69.1",
64
65
  "esbuild": "^0.28.0",
65
66
  "happy-dom": "^20.14.5",
66
67
  "isomorphic-dompurify": "^3.18.0",
68
+ "maplibre-gl": "6.11.2",
67
69
  "next": "16.2.11",
68
70
  "react": "19.2.4",
69
71
  "react-dom": "19.2.4",
@@ -1,7 +0,0 @@
1
- export interface ImageFocusRowProps {
2
- label: string;
3
- value: string;
4
- onChange: (next: string) => void;
5
- image?: string;
6
- }
7
- export default function ImageFocusRow({ label, value, onChange, image }: ImageFocusRowProps): import("react").JSX.Element;
@@ -1 +0,0 @@
1
- "use client";import{jsx as e}from"react/jsx-runtime";import{useState as l}from"react";import{Button as u,ImageFocusPoint as c}from"efficient-shell/ui";import f from"./TextStyleRow.js";import{say as o}from"../lib/editor-words.js";function p({label:t,value:n,onChange:s,image:i}){const[a,r]=l(!1);return e(f,{styleKey:"imageFocal",label:t,value:n,sampleStyle:{fontSize:11,fontVariantNumeric:"tabular-nums"},open:a,onToggle:()=>r(m=>!m),children:a?e(c,{label:o(t),value:n,onChange:s,image:i,emptyText:o("Choose a background image first — there’s nothing to focus yet."),actions:e(u,{variant:"secondary",size:"sm",onClick:()=>r(!1),children:o("Done")})}):null})}export{p as default};
@@ -1,6 +0,0 @@
1
- export declare const TEMPLATE_GLYPH_PATH = "M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z";
2
- export default function TemplateGlyph({ size, className, strokeWidth, }: {
3
- size?: number;
4
- className?: string;
5
- strokeWidth?: number;
6
- }): import("react").JSX.Element;
@@ -1 +0,0 @@
1
- import{jsx as o}from"react/jsx-runtime";const n="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z";function a({size:e=15,className:r,strokeWidth:t=1.7}){return o("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:t,strokeLinecap:"round",strokeLinejoin:"round",className:r,"aria-hidden":"true",focusable:"false",children:o("path",{d:n})})}export{n as TEMPLATE_GLYPH_PATH,a as default};
@@ -1,13 +0,0 @@
1
- import { type CSSProperties, type ReactNode } from "react";
2
- export interface TextStyleRowProps {
3
- styleKey: string;
4
- label: string;
5
- icon?: ReactNode;
6
- value?: ReactNode;
7
- sampleStyle: CSSProperties;
8
- open: boolean;
9
- onToggle: () => void;
10
- saved?: boolean;
11
- children?: ReactNode;
12
- }
13
- export default function TextStyleRow({ styleKey, label, icon, value, sampleStyle, open, onToggle, saved, children, }: TextStyleRowProps): import("react").JSX.Element;
@@ -1 +0,0 @@
1
- "use client";import{jsx as s,jsxs as i}from"react/jsx-runtime";import{ChevronIcon as f}from"efficient-easel/icons";import{Tooltip as m}from"efficient-shell/ui";import{say as p}from"../lib/editor-words.js";function h({styleKey:a,label:r,icon:t,value:e,sampleStyle:l,open:n,onToggle:o,saved:d=!1,children:c}){return i("div",{"data-sf-style-row":a,className:`sf-style-row${n?" is-open":""}`,children:[i("button",{type:"button",className:"sf-style-head","aria-expanded":n,onClick:o,children:[s("span",{className:"sf-style-caret","aria-hidden":"true",children:s(f,{size:13})}),t?s("span",{className:"es-field-icon",children:t}):null,s("span",{className:"sf-style-name",children:p(r)}),e!=null?typeof e=="string"?s(m,{content:e,className:"sf-style-sample-tip",children:s("span",{className:"sf-style-sample",style:l,children:e})}):s("span",{className:"sf-style-sample",style:l,children:e}):null,s("span",{className:`sf-style-slot${d?" is-saved":""}`,children:s(y,{})})]}),n&&s("div",{className:"sf-style-body",children:c})]})}function y(){return s("svg",{viewBox:"0 0 24 24",width:"14",height:"14",fill:"none",stroke:"currentColor",strokeWidth:3,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:s("polyline",{points:"20 6 9 17 4 12"})})}export{h as default};