@gemboss/gem-framework 0.15.0 → 0.18.11

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/dist/index.js CHANGED
@@ -19,6 +19,137 @@ function gemPartClass(part) {
19
19
  return GEM_PART_CLASS[part] ?? null;
20
20
  }
21
21
 
22
+ // ../../packages/gem-framework/src/section-dividers.ts
23
+ var spikes = (() => {
24
+ const teeth = [];
25
+ for (let x = 95; x > 0; x -= 5) teeth.push(`L${x} ${teeth.length % 2 === 0 ? 10 : 3}`);
26
+ return `M0 0 H100 V3 ${teeth.join(" ")} L0 ${teeth.length % 2 === 0 ? 10 : 3} Z`;
27
+ })();
28
+ var GEM_SECTION_DIVIDER_PATHS = Object.freeze({
29
+ wave: "M0 0 H100 V3 C80 9 70 -1 50 4 C30 9 20 1 0 4 Z",
30
+ slope: "M0 0 H100 V10 Z",
31
+ spikes,
32
+ arch: "M0 0 H100 V2 Q50 12 0 2 Z",
33
+ blob: "M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z"
34
+ });
35
+ function gemSectionDividers(props) {
36
+ const layers = [];
37
+ for (const edge of ["top", "bottom"]) {
38
+ const shape = props?.[`divider_${edge}`];
39
+ if (typeof shape === "string" && Object.hasOwn(GEM_SECTION_DIVIDER_PATHS, shape)) {
40
+ const known = shape;
41
+ layers.push({ edge, shape: known, path: GEM_SECTION_DIVIDER_PATHS[known] });
42
+ }
43
+ }
44
+ return layers;
45
+ }
46
+ function height(value) {
47
+ if (typeof value !== "number" && typeof value !== "string") return void 0;
48
+ if (typeof value === "string" && !/^\d+(?:\.\d+)?$/.test(value.trim())) return void 0;
49
+ const n = Number(value);
50
+ return Number.isFinite(n) && n >= 0 && n <= 320 ? n : void 0;
51
+ }
52
+ var NAMED_COLORS = /* @__PURE__ */ new Set([
53
+ "transparent",
54
+ "currentcolor",
55
+ "inherit",
56
+ "initial",
57
+ "unset",
58
+ "black",
59
+ "silver",
60
+ "gray",
61
+ "grey",
62
+ "white",
63
+ "maroon",
64
+ "red",
65
+ "purple",
66
+ "fuchsia",
67
+ "green",
68
+ "lime",
69
+ "olive",
70
+ "yellow",
71
+ "navy",
72
+ "blue",
73
+ "teal",
74
+ "aqua",
75
+ "orange"
76
+ ]);
77
+ var NUMBER = "[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?";
78
+ var CHANNEL = new RegExp(`^${NUMBER}%?$`, "i");
79
+ var PERCENT = new RegExp(`^${NUMBER}%$`, "i");
80
+ var ANGLE = new RegExp(`^${NUMBER}(?:deg|grad|rad|turn)?$`, "i");
81
+ var finite = (value) => Number.isFinite(Number.parseFloat(value));
82
+ var numeric = (value) => CHANNEL.test(value) && finite(value);
83
+ var percentage = (value) => PERCENT.test(value) && finite(value);
84
+ var angle = (value) => ANGLE.test(value) && finite(value);
85
+ function functionalColor(value) {
86
+ const match = /^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch)\(([^()]*)\)$/i.exec(value);
87
+ if (!match) return false;
88
+ const name = match[1].toLowerCase(), body = match[2].trim();
89
+ let channels, alpha;
90
+ if (body.includes(",")) {
91
+ if (!["rgb", "rgba", "hsl", "hsla"].includes(name) || body.includes("/")) return false;
92
+ const parts = body.split(",").map((part) => part.trim());
93
+ if (parts.length !== 3 && parts.length !== 4) return false;
94
+ channels = parts.slice(0, 3);
95
+ alpha = parts[3];
96
+ if ((name === "rgb" || name === "rgba") && new Set(channels.map((part) => part.endsWith("%"))).size !== 1) return false;
97
+ } else {
98
+ const parts = body.split("/");
99
+ if (parts.length > 2) return false;
100
+ channels = parts[0].trim().split(/\s+/);
101
+ alpha = parts[1]?.trim();
102
+ }
103
+ if (channels.length !== 3 || alpha !== void 0 && !numeric(alpha)) return false;
104
+ if (name === "rgb" || name === "rgba") return channels.every(numeric);
105
+ if (name === "hsl" || name === "hsla" || name === "hwb") {
106
+ return angle(channels[0]) && percentage(channels[1]) && percentage(channels[2]);
107
+ }
108
+ if (name === "lch" || name === "oklch") return numeric(channels[0]) && numeric(channels[1]) && angle(channels[2]);
109
+ return channels.every(numeric);
110
+ }
111
+ function color(value) {
112
+ if (typeof value !== "string") return void 0;
113
+ const v = value.trim();
114
+ if (!v || v.length > 128 || /[;{}<>\\]/.test(v)) return void 0;
115
+ if (/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(v) || NAMED_COLORS.has(v.toLowerCase())) return v;
116
+ if (functionalColor(v)) return v;
117
+ const token = /^var\(\s*--[A-Za-z0-9_-]+\s*(?:,\s*([^()]*))?\)$/.exec(v);
118
+ if (token && (token[1] === void 0 || color(token[1]))) return v;
119
+ return void 0;
120
+ }
121
+ function gemSectionDividerStyle(props, bandBackground) {
122
+ if (!gemSectionDividers(props).length) return {};
123
+ const value = props?.divider_flip;
124
+ const flip = value === true || value === "true" || value === 1 || value === "1";
125
+ return {
126
+ "--gem-element-divider-height": `${height(props?.divider_height) ?? 48}px`,
127
+ "--gem-element-divider-color": color(props?.divider_color) ?? color(bandBackground) ?? "var(--gem-section-bg, var(--gem-color-background))",
128
+ "--gem-element-divider-scale-x": flip ? "-1" : "1"
129
+ };
130
+ }
131
+ function gemSectionDividerResponsiveStyle(props) {
132
+ const n = height(props?.divider_height);
133
+ return n === void 0 ? {} : { "--gem-element-divider-height": `${n}px` };
134
+ }
135
+ var GEM_ELEMENT_SECTION_DIVIDER_CSS = [
136
+ ".gem-el-section[data-gem-element-dividers] { position: relative; }",
137
+ // A configured edge must escape the band's parallax clip. The media keeps its own clipped
138
+ // wrapper so releasing the root cannot expose the deliberately oversized moving background.
139
+ ".gem-el-section[data-gem-parallax][data-gem-element-dividers] { overflow: visible; isolation: auto; }",
140
+ ".gem-el-section[data-gem-element-dividers] > .gem-el-section__parallax-clip { position: absolute; inset: 0; overflow: hidden; border-radius: inherit; pointer-events: none; z-index: 0; }",
141
+ // Edge paint may extend into a neighbour with little padding. Only content, never its background,
142
+ // stacks above it. Guard on the current document's opted-in markup: a retained CSS resource after
143
+ // navigation must not change a page with no native dividers.
144
+ // An ordinary neighbouring parallax band also isolates its content by default. Release that
145
+ // stacking context on divider documents, retaining its existing media clip and direct markup.
146
+ "body:has(.gem-el-section[data-gem-element-dividers]) .gem-el-section[data-gem-parallax] { isolation: auto; }",
147
+ "body:has(.gem-el-section[data-gem-element-dividers]) .gem-el-section > .gem-el-section__inner { position: relative; z-index: 2; }",
148
+ ".gem-el-section > .gem-el-section__divider { position: absolute; display: block; inset-inline: 0; width: 100%; max-width: none; height: var(--gem-element-divider-height, 48px); color: var(--gem-element-divider-color, var(--gem-color-background)); overflow: hidden; pointer-events: none; z-index: 1; transform: scaleX(var(--gem-element-divider-scale-x, 1)); }",
149
+ ".gem-el-section > .gem-el-section__divider--bottom { top: 100%; }",
150
+ ".gem-el-section > .gem-el-section__divider--top { bottom: 100%; transform: scaleX(var(--gem-element-divider-scale-x, 1)) scaleY(-1); }"
151
+ ].join("\n");
152
+
22
153
  // ../../packages/gem-framework/src/form-set.ts
23
154
  var GEM_FORM_TOKEN_KEYS = [
24
155
  "radius",
@@ -76,7 +207,7 @@ var GEM_FORM_DEFAULTS = {
76
207
  checkboxAccent: "var(--gem-color-accent)",
77
208
  checkboxSize: "24px"
78
209
  };
79
- var GEM_FORM_VAR_SCOPE = "[data-gem-form-set], [data-gem-form-set] :is(input, select, textarea, button, form)";
210
+ var GEM_FORM_VAR_SCOPE = "[data-gem-form-set], [data-gem-form-set] :is(input, select, textarea, button, form, output)";
80
211
  function gemFormCssBlock(form) {
81
212
  const vars = gemFormCssVars(form);
82
213
  return vars.length ? `${GEM_FORM_VAR_SCOPE} {
@@ -130,6 +261,30 @@ var GEM_FORM_SETS = [
130
261
  }
131
262
  ];
132
263
  var gemFormSetsFor = (posture) => GEM_FORM_SETS.filter((s) => s.postures.includes(posture));
264
+ var GEM_SOFT_FIELD_FAMILIES = ["natural", "beauty", "home", "food"];
265
+ function deriveFormSetId(source) {
266
+ const norm = (v) => typeof v === "string" ? v.trim().toLowerCase() : "";
267
+ const radius = norm(source?.global_settings?.button_radius);
268
+ const style = norm(source?.global_settings?.button_style);
269
+ if (radius === "sharp" && style === "outline") return "hairline-underline";
270
+ if (radius === "pill" && style !== "soft") return "pill-joined";
271
+ if (style === "soft" || GEM_SOFT_FIELD_FAMILIES.includes(norm(source?.family))) return "soft-filled";
272
+ return "crisp-outline";
273
+ }
274
+ function gemPlanFormSet(plan) {
275
+ const tokens = plan?.tokens;
276
+ if (tokens && typeof tokens === "object" && Object.prototype.hasOwnProperty.call(tokens, "form")) {
277
+ const own = tokens.form;
278
+ if (own === null) return null;
279
+ if (own && typeof own === "object" && !Array.isArray(own)) {
280
+ if (hasForm(own)) return own;
281
+ const byId = GEM_FORM_SETS.find((s) => s.id === own.id);
282
+ if (byId) return { ...byId.form };
283
+ }
284
+ }
285
+ const set = GEM_FORM_SETS.find((s) => s.id === deriveFormSetId({ family: plan?.style_pack?.family, global_settings: plan?.global_settings }));
286
+ return set ? { ...set.form } : null;
287
+ }
133
288
 
134
289
  // ../../packages/gem-framework/src/plan-shape.ts
135
290
  function gemStyleSpaceFor(plan) {
@@ -230,13 +385,13 @@ function mixHex(from, to, t) {
230
385
  const channel = (x, y) => Math.round(x * (1 - t) + y * t).toString(16).padStart(2, "0");
231
386
  return `#${channel(a.r, b.r)}${channel(a.g, b.g)}${channel(a.b, b.b)}`;
232
387
  }
233
- function aaTextColor(color, backgrounds, min = MIN_TEXT_CONTRAST) {
234
- const from = normalizeHex(color);
388
+ function aaTextColor(color2, backgrounds, min = MIN_TEXT_CONTRAST) {
389
+ const from = normalizeHex(color2);
235
390
  const bgs = backgrounds.map(normalizeHex).filter((bg) => Boolean(bg));
236
- if (!from || !bgs.length) return color;
391
+ if (!from || !bgs.length) return color2;
237
392
  const worst = (c) => Math.min(...bgs.map((bg) => hexContrastRatio(c, bg)));
238
- let best = { color, score: worst(from) };
239
- if (best.score >= min) return color;
393
+ let best = { color: color2, score: worst(from) };
394
+ if (best.score >= min) return color2;
240
395
  let pass;
241
396
  for (const toward of ["#000000", "#ffffff"]) {
242
397
  for (let step = 1; step <= 50; step++) {
@@ -280,7 +435,7 @@ function gemButtonGradientEndCss(accent = "var(--gem-color-accent)", accentText
280
435
  }
281
436
 
282
437
  // ../../packages/gem-framework/src/tokens.ts
283
- var gemFrameworkVersion = "0.15.0";
438
+ var gemFrameworkVersion = "0.18.11";
284
439
  var GEM_FOCUS_RING_CSS_VAR = "--gem-focus-ring: 2px solid var(--gem-color-accent);";
285
440
  var GEM_TAP_MIN_CSS_VAR = "--gem-tap-min: 44px;";
286
441
  var GEM_FOCUS_OFFSET_CSS_VAR = "--gem-focus-offset: 3px;";
@@ -443,17 +598,17 @@ function gemBandScheme(c, band) {
443
598
  danger: aaTextColor(c.danger, grounds)
444
599
  };
445
600
  }
446
- function gemBandSchemeColors(color = {}) {
601
+ function gemBandSchemeColors(color2 = {}) {
447
602
  const c = Object.fromEntries(
448
603
  requiredGemTokenKeys.color.map((key) => {
449
- const value = color?.[key];
604
+ const value = color2?.[key];
450
605
  return [key, typeof value === "string" && value.trim() ? stripImportant(value) : defaultGemTokens.color[key]];
451
606
  })
452
607
  );
453
608
  return { dark: gemBandScheme(c, "dark"), accent: gemBandScheme(c, "accent") };
454
609
  }
455
- function gemBandSchemeCssVars(color = {}) {
456
- const schemes = gemBandSchemeColors(color);
610
+ function gemBandSchemeCssVars(color2 = {}) {
611
+ const schemes = gemBandSchemeColors(color2);
457
612
  return GEM_BAND_SCHEMES.flatMap(
458
613
  (band) => requiredGemTokenKeys.color.map((key) => `--gem-scheme-${band}-${slugify(key)}: ${schemes[band][key]};`)
459
614
  );
@@ -749,12 +904,12 @@ function gemMobileTokenCssVars(tokens, phone = readPhoneTokens(tokens)) {
749
904
  var rampKey = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
750
905
  var isRampKey = (k) => GEM_TYPE_RAMP_KEYS.some((r) => rampKey(r) === rampKey(k));
751
906
  var LENGTH = /^\d*\.?\d+(px|rem|em)$/;
752
- var NUMBER = /^\d*\.?\d+$/;
907
+ var NUMBER2 = /^\d*\.?\d+$/;
753
908
  var typeRampValue = (typography, key) => {
754
909
  const hit = Object.entries(typography ?? {}).find(([k]) => rampKey(k) === rampKey(key));
755
910
  const v = hit == null || hit[1] == null ? "" : stripImportant(hit[1]).trim();
756
911
  if (!v) return null;
757
- return (key === "scale" ? NUMBER.test(v) && Number(v) > 0 : LENGTH.test(v)) ? v : null;
912
+ return (key === "scale" ? NUMBER2.test(v) && Number(v) > 0 : LENGTH.test(v)) ? v : null;
758
913
  };
759
914
  var GEM_TYPE_MOBILE_MAX = "749px";
760
915
  function gemTypeRampCssVars(typography) {
@@ -833,6 +988,14 @@ var GEM_EDITION_SUPPORT_MONTHS = 6;
833
988
  var EDITION_2_CSS = [
834
989
  `:where(&) { ${GEM_HEADING_SIZE_CSS_VARS.filter((decl) => !decl.startsWith("--gem-section-heading-size-mobile") && !decl.startsWith("--gem-hero-heading-size-mobile")).join(" ")} }`
835
990
  ];
991
+ var EDITION_3_ELEMENTS = [
992
+ `:where(&) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }`,
993
+ `:where(&) .gem-product-card-grid--quick-view-grid .gem-product-card__media > .gem-product-card__badges { display: flex; }`,
994
+ `@media (max-width: 989px) { :where(&) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid[data-gem-product-grid] { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); } }`,
995
+ `:where(&) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) { position: relative; inset: auto; z-index: auto; opacity: 1; width: min(var(--gem-layout-page, 1200px), calc(100% - var(--gem-layout-gutter, 32px))); margin: clamp(24px, 5vw, 48px) auto 0; background: none; color: var(--gem-color-text); border-radius: 0; }`,
996
+ `:where(&) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) .gem-hero__heading { color: var(--gem-color-text); }`,
997
+ `:where(&) .gem-section.gem-hero.gem-hero-banner--compact-collection:not(:has(> .gem-hero__bg)) { padding-block-end: 0; }`
998
+ ];
836
999
  var GEM_FRAMEWORK_EDITIONS = [
837
1000
  { edition: 1, since: "0.1.0", released: "2026-07-04", changes: [], css: [] },
838
1001
  {
@@ -841,6 +1004,18 @@ var GEM_FRAMEWORK_EDITIONS = [
841
1004
  released: "2026-10-10",
842
1005
  changes: ["Section headings and hero titles follow your type scale on tablets and computers. Phones keep their current size."],
843
1006
  css: EDITION_2_CSS
1007
+ },
1008
+ {
1009
+ // Edition 3. Collection pages at Dawn's tiers (12/10/2026, measured on a "Start from scratch" theme): EDITION_3_ELEMENTS.
1010
+ edition: 3,
1011
+ since: "0.17.0",
1012
+ released: "2026-10-12",
1013
+ changes: [
1014
+ "Collection grids: each product photo fills its column, phones and tablets show your mobile column count, and Sale and Sold out badges appear.",
1015
+ "The collection title sits on the page, lined up with the products, in your text colour."
1016
+ ],
1017
+ css: [],
1018
+ elements: EDITION_3_ELEMENTS
844
1019
  }
845
1020
  ];
846
1021
  var LATEST_GEM_FRAMEWORK_EDITION = GEM_FRAMEWORK_EDITIONS[GEM_FRAMEWORK_EDITIONS.length - 1].edition;
@@ -862,6 +1037,9 @@ function addEditionSupportMonths(released) {
862
1037
  function gemFrameworkEditionScope(edition) {
863
1038
  return GEM_FRAMEWORK_EDITIONS.filter((e) => e.edition >= edition).map((e) => `[data-gem-edition='${e.edition}']`).join(", ");
864
1039
  }
1040
+ function gemFrameworkEditionElementCss() {
1041
+ return GEM_FRAMEWORK_EDITIONS.flatMap((e) => (e.elements ?? []).map((rule) => rule.replaceAll("&", gemFrameworkEditionScope(e.edition)))).join("\n");
1042
+ }
865
1043
  function gemFrameworkEditionCss() {
866
1044
  return GEM_FRAMEWORK_EDITIONS.flatMap((e) => e.css.map((rule) => rule.replaceAll("&", gemFrameworkEditionScope(e.edition)))).join("\n");
867
1045
  }
@@ -1425,18 +1603,8 @@ var GEM_POPUP_SHELL_CSS = GEM_POPUP_SHELL_CSS_LINES.join("\n");
1425
1603
 
1426
1604
  // ../../packages/gem-framework/src/primitives.ts
1427
1605
  var dividerMask = (path) => `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='${path}'/%3E%3C/svg%3E")`;
1428
- var spikePath = (() => {
1429
- const teeth = [];
1430
- for (let x = 95; x > 0; x -= 5) teeth.push(`L${x} ${teeth.length % 2 === 0 ? 10 : 3}`);
1431
- return `M0 0 H100 V3 ${teeth.join(" ")} L0 ${teeth.length % 2 === 0 ? 10 : 3} Z`;
1432
- })();
1433
- var DIVIDER_SHAPES = {
1434
- wave: "M0 0 H100 V3 C80 9 70 -1 50 4 C30 9 20 1 0 4 Z",
1435
- slope: "M0 0 H100 V10 Z",
1436
- spikes: spikePath,
1437
- arch: "M0 0 H100 V2 Q50 12 0 2 Z",
1438
- blob: "M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z"
1439
- };
1606
+ var knownValues = (attr, values) => `:where(${values.map((v) => `[${attr}='${v}']`).join(", ")})`;
1607
+ var DIVIDER_SHAPES = Object.keys(GEM_SECTION_DIVIDER_PATHS);
1440
1608
  var GEM_DIVIDER_CSS_LINES = [
1441
1609
  // The clip is WIDENED, not removed. `.gem-section` sets `overflow: clip` and the wedge is drawn
1442
1610
  // at `top: 100%` / `bottom: 100%` — entirely outside the box — so the parent deleted every
@@ -1447,7 +1615,8 @@ var GEM_DIVIDER_CSS_LINES = [
1447
1615
  // it entirely would paint square corners over that radius. A browser without
1448
1616
  // `overflow-clip-margin` keeps plain `clip` — the divider stays hidden, which is today's
1449
1617
  // behaviour rather than a broken layout.
1450
- ":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top], :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom] { position: relative; overflow: clip; overflow-clip-margin: var(--gem-divider-height, 48px); }",
1618
+ // Only for a shape that has a mask (knownValues): a "none" edge used to widen the clip by 48px for nothing.
1619
+ `:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top]${knownValues("data-gem-divider-top", DIVIDER_SHAPES)}, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom]${knownValues("data-gem-divider-bottom", DIVIDER_SHAPES)} { position: relative; overflow: clip; overflow-clip-margin: var(--gem-divider-height, 48px); }`,
1451
1620
  // Shared geometry. `top: 100%` / `bottom: 100%` put it outside the box; z-index 1 keeps it above the
1452
1621
  // neighbouring band's background. pointer-events: none so it never eats a click near the seam.
1453
1622
  // Qualified by SHAPE. Unqualified, any value paints a solid 48px slab — including the literal
@@ -1457,7 +1626,7 @@ var GEM_DIVIDER_CSS_LINES = [
1457
1626
  ":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom]::after { top: 100%; }",
1458
1627
  // The top edge reuses the same masks, mirrored, so five shapes cover both edges.
1459
1628
  ":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top]::before { bottom: 100%; transform: scaleY(-1); }",
1460
- ...Object.entries(DIVIDER_SHAPES).flatMap(([name, path]) => {
1629
+ ...Object.entries(GEM_SECTION_DIVIDER_PATHS).flatMap(([name, path]) => {
1461
1630
  const mask = dividerMask(path);
1462
1631
  return [
1463
1632
  `:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='${name}']::after { -webkit-mask-image: ${mask}; mask-image: ${mask}; }`,
@@ -1466,8 +1635,10 @@ var GEM_DIVIDER_CSS_LINES = [
1466
1635
  })
1467
1636
  ];
1468
1637
  var GEM_DIVIDER_CSS = GEM_DIVIDER_CSS_LINES.join("\n");
1638
+ var GEM_CARD_STYLES = ["outline", "shadow", "filled"];
1469
1639
  var GEM_CARD_CSS_LINES = [
1470
- ":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card] :is(.gem-section__inner, [class*='__inner']) { border-radius: var(--gem-radius-md); padding: var(--gem-space-xl); }",
1640
+ // Only for a style that draws (knownValues): "none", the knob's revert value, arrives here as an attribute too.
1641
+ `:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card]${knownValues("data-gem-card", GEM_CARD_STYLES)} :is(.gem-section__inner, [class*='__inner']) { border-radius: var(--gem-radius-md); padding: var(--gem-space-xl); }`,
1471
1642
  ":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='outline'] :is(.gem-section__inner, [class*='__inner']) { border: 1px solid var(--gem-color-border); }",
1472
1643
  ":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='shadow'] :is(.gem-section__inner, [class*='__inner']) { background: var(--gem-color-surface); box-shadow: var(--gem-shadow-md); }",
1473
1644
  // `filled` tints the section's own ground rather than reaching for a fixed colour, so it stays
@@ -1761,7 +1932,7 @@ function createGemBaseCssParts(profile) {
1761
1932
  ].join("\n");
1762
1933
  return {
1763
1934
  // Edition rules close the primitives (editions.ts): custom properties on <html>, scoped :where([data-gem-edition]).
1764
- primitives: [reset, "", createPrimitiveCss(), "", gemFrameworkEditionCss(), "", ""].join("\n"),
1935
+ primitives: [reset, "", createPrimitiveCss(), "", gemFrameworkEditionElementCss(), "", gemFrameworkEditionCss(), "", ""].join("\n"),
1765
1936
  sections: [createShellCss(), "", createSectionCss(), "", createThemeProfileCss(profile)].join("\n")
1766
1937
  };
1767
1938
  }
@@ -1771,7 +1942,11 @@ function createThemeProfileCss(profile = "dawn-parity") {
1771
1942
  function createMorphProfileCss() {
1772
1943
  return [
1773
1944
  "/* Gem morph profile: style packs and section presets provide visual transformation above core primitives. */",
1774
- ":root { --gem-profile-mode: morph; }"
1945
+ ":root { --gem-profile-mode: morph; }",
1946
+ // A morph theme's quick-view card is a box drawn from its card tokens (the .gem-card primitive). Edition 3 unboxes this
1947
+ // card for Dawn's collection page; restated here one class higher, so a morph theme keeps its box and its card knobs on
1948
+ // every edition (review of #764). The same values as .gem-card: nothing changes for a morph store on edition 1 or 2.
1949
+ ".gem-product-card-grid--quick-view-grid .gem-card.gem-product-card-grid__card { border: var(--gem-border-subtle, 1px solid var(--gem-color-border)); border-radius: var(--gem-card-radius, var(--gem-radius-md)); background: var(--gem-card-background, var(--gem-color-surface)); padding: var(--gem-component-card-padding, var(--gem-space-md)); box-shadow: var(--gem-shadow-sm, none); }"
1775
1950
  ].join("\n");
1776
1951
  }
1777
1952
  function createPrimitiveCss() {
@@ -2217,17 +2392,25 @@ function createPrimitiveCss() {
2217
2392
  // .gem-rating above is a type rule for a text label; every actual star row in the theme was a
2218
2393
  // hardcoded five-glyph literal, so a captured 4-of-5 (or a half star) had nowhere to land. The
2219
2394
  // fill is a percentage-width overlay, so fractional values render exactly instead of rounding.
2395
+ // SVG stars (GemFactory gap #45, 0.18.11): real review widgets draw each star as an SVG (solid + outline), and the
2396
+ // eight product-reviews sections rebuilt in GemFactory put those SVGs in __base / __fill. Two things broke the fill
2397
+ // (measured 12/10/2026): the base `svg { max-width: 100% }` shrank the SVGs in the clipped __fill to the fill's
2398
+ // width (4.2 drew as five squeezed stars), and a .gem-stars that is a stretched grid / flex item computed its
2399
+ // percentage against the stretched width (3 of 5 drew as 5). The row is as wide as its stars, always, and a star
2400
+ // keeps its own size inside the clip.
2220
2401
  ".gem-stars {",
2221
2402
  " --gem-stars-total: 5;",
2222
2403
  " --gem-stars-value: 5;",
2223
2404
  " position: relative;",
2224
2405
  " display: inline-block;",
2406
+ " width: max-content;",
2225
2407
  " line-height: 1;",
2226
2408
  " font-size: var(--gem-text-sm, 0.8125rem);",
2227
2409
  " letter-spacing: 0.1em;",
2228
2410
  " color: var(--gem-color-border);",
2229
2411
  "}",
2230
2412
  ".gem-stars__base, .gem-stars__fill { display: block; white-space: nowrap; }",
2413
+ ".gem-stars :is(svg, img) { max-width: none; flex: none; }",
2231
2414
  ".gem-stars__fill {",
2232
2415
  " position: absolute;",
2233
2416
  " inset: 0;",
@@ -3733,7 +3916,7 @@ function createDawnParityCss() {
3733
3916
  ".gem-card, .gem-product-card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }",
3734
3917
  ".gem-product-card__media, .gem-media { border: 0; border-radius: 0; box-shadow: none; background: rgb(244 244 244); }",
3735
3918
  ".gem-product-card__media { display: block; position: relative; width: 100%; height: 0; min-block-size: 0; padding-bottom: 100%; aspect-ratio: auto; overflow: hidden; }",
3736
- ".gem-featured-collection--editorial-grid .gem-product-card__media, .gem-product-card-grid--quick-view-grid .gem-product-card__media, .gem-main-search .gem-product-card__media, .gem-cart-recommendations--dawn-empty .gem-product-card__media { min-block-size: 0 !important; }",
3919
+ ".gem-featured-collection--editorial-grid .gem-product-card__media, .gem-product-card-grid--quick-view-grid .gem-product-card__media, .gem-main-search .gem-product-card__media, .gem-cart-recommendations--dawn-empty .gem-product-card__media, .gem-recommended-products--product-rail .gem-product-card__media, .gem-cart-recommendations--product-rail .gem-product-card__media { min-block-size: 0 !important; }",
3737
3920
  ".gem-product-card__media > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }",
3738
3921
  // Home product grids show generated/lifestyle imagery (not catalog product-on-white shots), so
3739
3922
  // `contain` letterboxes them inside the square card (looks misaligned / floating off the box).
@@ -3750,7 +3933,7 @@ function createDawnParityCss() {
3750
3933
  ".gem-cart-recommendations--dawn-empty .gem-product-card__quick-actions { display: none; }",
3751
3934
  ".gem-featured-collection--editorial-grid > .gem-section__inner > .gem-section__heading, .gem-featured-collection--editorial-grid > .gem-section__inner > .gem-section__body { display: none; }",
3752
3935
  ".gem-featured-collection--editorial-grid { padding-block: 28px 36px; }",
3753
- ".gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-columns, 4), minmax(0, 1fr)); gap: 8px; }",
3936
+ ".gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid, .gem-recommended-products--product-rail .gem-section__grid, .gem-cart-recommendations--product-rail .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-columns, 4), minmax(0, 1fr)); gap: 8px; }",
3754
3937
  ".gem-product-card-grid--quick-view-grid { padding-top: 0; }",
3755
3938
  ".gem-product-card-grid--quick-view-grid .gem-section__heading:empty { display: none; }",
3756
3939
  // Hide a compare-at only when the price ISN'T actually on sale. The rule used to hide it
@@ -3920,14 +4103,16 @@ function createDawnParityMobileCss() {
3920
4103
  " .gem-hero-banner--compact-collection { padding-block: 25px; }",
3921
4104
  " .gem-hero-banner--compact-collection .gem-section__heading { font-size: 30px; line-height: 39px; }",
3922
4105
  " .gem-product-card-grid--quick-view-grid > .gem-section__inner, .gem-main-search > .gem-section__inner { width: calc(100% - 30px); }",
3923
- " .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid { gap: 4px !important; }",
4106
+ // 8px on a phone too, as the featured collection and the two product rails: the cards are Dawn's flat cards, and with
4107
+ // no padding of their own a 4px gap left two columns of titles 4px apart at 320 (review of gemsite #951).
4108
+ " .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid { gap: 8px !important; }",
3924
4109
  " .gem-product-card__content { padding-top: 8px; gap: 4px; }",
3925
4110
  " .gem-product-card__title { font-size: 15px; line-height: 24px; }",
3926
4111
  " .gem-product-card__price, .gem-product-card__price .gem-price { font-size: 16px; line-height: 24px; }",
3927
4112
  // Fallback = the profile's own phone heading, declared once as --gem-heading-phone-cap on the :root above.
3928
4113
  " .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, var(--gem-heading-phone-cap)); }",
3929
4114
  " .gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size-mobile, var(--gem-heading-phone-cap)); }",
3930
- " .gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); }",
4115
+ " .gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid, .gem-recommended-products--product-rail .gem-section__grid, .gem-cart-recommendations--product-rail .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); }",
3931
4116
  " .gem-product-main[data-gem-product-main-layout='dawn-main'] > .gem-section__inner { width: calc(100% - 30px); }",
3932
4117
  " .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-media-gallery__frame { display: flex; gap: 0; overflow-x: auto; scroll-snap-type: x mandatory; }",
3933
4118
  " .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-media-gallery__item { flex: 0 0 100%; scroll-snap-align: start; aspect-ratio: 1 / 1; }",
@@ -6344,7 +6529,8 @@ var PREVIEW_MEDIA_CSS = [
6344
6529
  function renderGemPreviewDocument(plan, options = {}) {
6345
6530
  const page = options.page ?? "home";
6346
6531
  const profile = plan.theme_profile ?? "dawn-parity";
6347
- const baseCss = createGemThemeCss(plan.tokens, ":root", { profile, ...gemPlanShapeKnobs(plan) });
6532
+ const formSet = gemPlanFormSet(plan);
6533
+ const baseCss = createGemThemeCss({ ...plan.tokens, form: formSet ?? void 0 }, ":root", { profile, ...gemPlanShapeKnobs(plan) });
6348
6534
  const overflowGuardCss = "html,body{max-width:100%;overflow-x:hidden}*,*::before,*::after{box-sizing:border-box}";
6349
6535
  const css = [baseCss, overflowGuardCss, CHROME_VARIANT_CSS, options.sectionCss, PREVIEW_MEDIA_CSS].filter(Boolean).join("\n");
6350
6536
  const runtimeJs = options.includeRuntime === false ? "" : createGemRuntimeBootstrap(planToRuntimeBootstrapConfig(plan));
@@ -6352,7 +6538,7 @@ function renderGemPreviewDocument(plan, options = {}) {
6352
6538
  const footer = renderGemShellFooter(plan);
6353
6539
  const sectionsHtml = renderGemPageSections(plan, page, options.sectionMarkup ?? {});
6354
6540
  const title = options.title ?? `${plan.name} preview`;
6355
- const formAttrs = Object.entries(gemFormAttributes(plan.tokens.form)).map(([k, v]) => ` ${k}="${escapeAttribute(v)}"`).join("");
6541
+ const formAttrs = Object.entries(gemFormAttributes(formSet ?? void 0)).map(([k, v]) => ` ${k}="${escapeAttribute(v)}"`).join("");
6356
6542
  const bodyClass = ["gem-theme", `gem-theme--${slugify(profile)}`, options.bodyClassName].filter(Boolean).join(" ");
6357
6543
  const edition = gemFrameworkEditionOf(plan.framework_edition);
6358
6544
  const editionAttr = edition > 1 ? ` data-gem-edition="${edition}"` : "";
@@ -6590,7 +6776,7 @@ function renderGemSectionMarkup(section) {
6590
6776
  const widthRaw = settings.gem_content_width;
6591
6777
  const width = widthRaw === "full" || widthRaw === "contained" || widthRaw === "narrow" ? widthRaw : "";
6592
6778
  const heightRaw = settings.gem_section_height;
6593
- const height = heightRaw === "short" || heightRaw === "normal" || heightRaw === "tall" ? heightRaw : "";
6779
+ const height2 = heightRaw === "short" || heightRaw === "normal" || heightRaw === "tall" ? heightRaw : "";
6594
6780
  const columnsRaw = settings.gem_grid_columns;
6595
6781
  const columns = columnsRaw === "2" || columnsRaw === "3" || columnsRaw === "4" ? columnsRaw : "";
6596
6782
  const bgRaw = settings.gem_section_background;
@@ -6615,7 +6801,7 @@ function renderGemSectionMarkup(section) {
6615
6801
  media ? ` data-gem-media-position="${media}"` : "",
6616
6802
  spacing ? ` data-gem-content-spacing="${spacing}"` : "",
6617
6803
  width ? ` data-gem-content-width="${width}"` : "",
6618
- height ? ` data-gem-section-height="${height}"` : "",
6804
+ height2 ? ` data-gem-section-height="${height2}"` : "",
6619
6805
  columns ? ` data-gem-grid-columns="${columns}"` : "",
6620
6806
  background ? ` data-gem-section-background="${background}"` : "",
6621
6807
  priceColor ? ` data-gem-price-color="${priceColor}"` : "",
@@ -7422,6 +7608,7 @@ export {
7422
7608
  GEM_BUTTON_TOKEN_SHAPES,
7423
7609
  GEM_COUNTDOWN_ENDED_CSS,
7424
7610
  GEM_EDITION_SUPPORT_MONTHS,
7611
+ GEM_ELEMENT_SECTION_DIVIDER_CSS,
7425
7612
  GEM_FIELD_JOINS,
7426
7613
  GEM_FIELD_SHAPES,
7427
7614
  GEM_FORM_CSS_VARIABLES,
@@ -7444,6 +7631,8 @@ export {
7444
7631
  GEM_ROW_CSS,
7445
7632
  GEM_ROW_CSS_VARIABLES,
7446
7633
  GEM_ROW_GAP_CSS,
7634
+ GEM_SECTION_DIVIDER_PATHS,
7635
+ GEM_SOFT_FIELD_FAMILIES,
7447
7636
  LATEST_GEM_FRAMEWORK_EDITION,
7448
7637
  MIN_TEXT_CONTRAST,
7449
7638
  MODERN_MINERAL_FAMILY_PACK_ID,
@@ -7473,6 +7662,7 @@ export {
7473
7662
  createThemeProfileCss,
7474
7663
  defaultGemTokens,
7475
7664
  defaultSelection,
7665
+ deriveFormSetId,
7476
7666
  gemBandSchemeColors,
7477
7667
  gemBandSchemeCssVars,
7478
7668
  gemBandSchemeDeclarations,
@@ -7484,6 +7674,7 @@ export {
7484
7674
  gemFormCssVars,
7485
7675
  gemFormSetsFor,
7486
7676
  gemFrameworkEditionCss,
7677
+ gemFrameworkEditionElementCss,
7487
7678
  gemFrameworkEditionOf,
7488
7679
  gemFrameworkEditionScope,
7489
7680
  gemFrameworkEditionSupportedUntil,
@@ -7495,9 +7686,13 @@ export {
7495
7686
  gemMotionScaleCssVars,
7496
7687
  gemPartClass,
7497
7688
  gemPhoneTokenTarget,
7689
+ gemPlanFormSet,
7498
7690
  gemPlanShapeKnobs,
7499
7691
  gemRuntimePluginModuleKeys,
7500
7692
  gemRuntimePluginSources,
7693
+ gemSectionDividerResponsiveStyle,
7694
+ gemSectionDividerStyle,
7695
+ gemSectionDividers,
7501
7696
  getGemFrameworkValidationRule,
7502
7697
  hexContrastRatio,
7503
7698
  hexRelativeLuminance,
@@ -495,12 +495,14 @@ a {
495
495
  --gem-stars-value: 5;
496
496
  position: relative;
497
497
  display: inline-block;
498
+ width: max-content;
498
499
  line-height: 1;
499
500
  font-size: var(--gem-text-sm, 0.8125rem);
500
501
  letter-spacing: 0.1em;
501
502
  color: var(--gem-color-border);
502
503
  }
503
504
  .gem-stars__base, .gem-stars__fill { display: block; white-space: nowrap; }
505
+ .gem-stars :is(svg, img) { max-width: none; flex: none; }
504
506
  .gem-stars__fill {
505
507
  position: absolute;
506
508
  inset: 0;
@@ -1051,7 +1053,7 @@ a {
1051
1053
  [data-gem-ambient] { --gem-ambient-amp: 2px; animation-duration: 12s; }
1052
1054
  [data-gem-reveal] { transition: none; opacity: 1; transform: none; }
1053
1055
  }
1054
- :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top], :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom] { position: relative; overflow: clip; overflow-clip-margin: var(--gem-divider-height, 48px); }
1056
+ :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top]:where([data-gem-divider-top='wave'], [data-gem-divider-top='slope'], [data-gem-divider-top='spikes'], [data-gem-divider-top='arch'], [data-gem-divider-top='blob']), :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom]:where([data-gem-divider-bottom='wave'], [data-gem-divider-bottom='slope'], [data-gem-divider-bottom='spikes'], [data-gem-divider-bottom='arch'], [data-gem-divider-bottom='blob']) { position: relative; overflow: clip; overflow-clip-margin: var(--gem-divider-height, 48px); }
1055
1057
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='wave']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='slope']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='spikes']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='arch']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='blob']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='wave']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='slope']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='spikes']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='arch']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='blob']::before { content: ''; position: absolute; left: 0; right: 0; z-index: 1; pointer-events: none; height: var(--gem-divider-height, 48px); background: var(--gem-section-bg, var(--gem-color-background)); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
1056
1058
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom]::after { top: 100%; }
1057
1059
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top]::before { bottom: 100%; transform: scaleY(-1); }
@@ -1065,7 +1067,7 @@ a {
1065
1067
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='arch']::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V2 Q50 12 0 2 Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V2 Q50 12 0 2 Z'/%3E%3C/svg%3E"); }
1066
1068
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='blob']::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); }
1067
1069
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='blob']::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); }
1068
- :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card] :is(.gem-section__inner, [class*='__inner']) { border-radius: var(--gem-radius-md); padding: var(--gem-space-xl); }
1070
+ :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card]:where([data-gem-card='outline'], [data-gem-card='shadow'], [data-gem-card='filled']) :is(.gem-section__inner, [class*='__inner']) { border-radius: var(--gem-radius-md); padding: var(--gem-space-xl); }
1069
1071
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='outline'] :is(.gem-section__inner, [class*='__inner']) { border: 1px solid var(--gem-color-border); }
1070
1072
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='shadow'] :is(.gem-section__inner, [class*='__inner']) { background: var(--gem-color-surface); box-shadow: var(--gem-shadow-md); }
1071
1073
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='filled'] :is(.gem-section__inner, [class*='__inner']) { background: color-mix(in srgb, currentColor 6%, transparent); }
@@ -1435,5 +1437,12 @@ a {
1435
1437
  .gem-collection-filter-toolbar__active-filter, .gem-collection-filter-toolbar__clear { min-height: 2.25rem; align-items: center; }
1436
1438
  }
1437
1439
 
1438
- :where([data-gem-edition='2']) { --gem-section-heading-size: calc(var(--gem-h2) * var(--gem-typography-heading-scale, 1)); --gem-hero-heading-size: calc(var(--gem-h1) * var(--gem-typography-heading-scale, 1)); }
1440
+ :where([data-gem-edition='3']) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }
1441
+ :where([data-gem-edition='3']) .gem-product-card-grid--quick-view-grid .gem-product-card__media > .gem-product-card__badges { display: flex; }
1442
+ @media (max-width: 989px) { :where([data-gem-edition='3']) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid[data-gem-product-grid] { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); } }
1443
+ :where([data-gem-edition='3']) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) { position: relative; inset: auto; z-index: auto; opacity: 1; width: min(var(--gem-layout-page, 1200px), calc(100% - var(--gem-layout-gutter, 32px))); margin: clamp(24px, 5vw, 48px) auto 0; background: none; color: var(--gem-color-text); border-radius: 0; }
1444
+ :where([data-gem-edition='3']) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) .gem-hero__heading { color: var(--gem-color-text); }
1445
+ :where([data-gem-edition='3']) .gem-section.gem-hero.gem-hero-banner--compact-collection:not(:has(> .gem-hero__bg)) { padding-block-end: 0; }
1446
+
1447
+ :where([data-gem-edition='2'], [data-gem-edition='3']) { --gem-section-heading-size: calc(var(--gem-h2) * var(--gem-typography-heading-scale, 1)); --gem-hero-heading-size: calc(var(--gem-h1) * var(--gem-typography-heading-scale, 1)); }
1439
1448