@gemboss/gem-framework 0.9.8 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/SOURCE.json +3 -3
- package/dist/gem-framework.css +27 -37
- package/dist/index.js +215 -56
- package/dist/primitives.css +9 -23
- package/dist/section-gate.js +192 -51
- package/dist/sections.css +18 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -191,7 +191,7 @@ function gemPlanShapeKnobs(plan) {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
// ../../packages/gem-framework/src/tokens.ts
|
|
194
|
-
var gemFrameworkVersion = "0.
|
|
194
|
+
var gemFrameworkVersion = "0.10.0";
|
|
195
195
|
var GEM_FOCUS_RING_CSS_VAR = "--gem-focus-ring: 2px solid var(--gem-color-accent);";
|
|
196
196
|
var GEM_TAP_MIN_CSS_VAR = "--gem-tap-min: 44px;";
|
|
197
197
|
var GEM_FOCUS_OFFSET_CSS_VAR = "--gem-focus-offset: 3px;";
|
|
@@ -225,6 +225,73 @@ var GEM_ROW_CSS_VARIABLES = [
|
|
|
225
225
|
"--gem-row-gap",
|
|
226
226
|
"--gem-row-gap-y"
|
|
227
227
|
];
|
|
228
|
+
var GEM_BUTTON_TOKEN_KEYS = {
|
|
229
|
+
radius: ["button"],
|
|
230
|
+
typography: ["buttonWeight", "buttonTransform", "buttonLetterSpacing"]
|
|
231
|
+
};
|
|
232
|
+
var GEM_BUTTON_TOKEN_SHAPES = {
|
|
233
|
+
"radius.button": /^(0|\d*\.?\d+(px|rem|em|%)|var\(--gem-radius-[a-z0-9-]+\))$/i,
|
|
234
|
+
"typography.buttonWeight": /^(normal|bold|\d{1,4})$/i,
|
|
235
|
+
"typography.buttonTransform": /^(none|uppercase|lowercase|capitalize)$/i,
|
|
236
|
+
"typography.buttonLetterSpacing": /^(normal|0|-?\d*\.?\d+(em|px|rem))$/i
|
|
237
|
+
};
|
|
238
|
+
var buttonKeyNorm = (k) => k.toLowerCase().replace(/[_-]/g, "");
|
|
239
|
+
function buttonTokenValue(group, key, value) {
|
|
240
|
+
const shape = GEM_BUTTON_TOKEN_SHAPES[`${group}.${key}`];
|
|
241
|
+
if (!shape) return null;
|
|
242
|
+
const v = typeof value === "number" ? String(value) : typeof value === "string" ? stripImportant(value).trim() : "";
|
|
243
|
+
if (!shape.test(v)) return null;
|
|
244
|
+
if (key === "buttonWeight" && /^\d+$/.test(v) && (Number(v) < 1 || Number(v) > 1e3)) return null;
|
|
245
|
+
if (key === "button" && v === "0") return "0px";
|
|
246
|
+
return /^[a-z]+$/i.test(v) ? v.toLowerCase() : v.replace(/(px|rem|em)$/i, (u) => u.toLowerCase());
|
|
247
|
+
}
|
|
248
|
+
function readButtonTokens(tokens) {
|
|
249
|
+
const out = { groups: {}, values: {}, invalid: [] };
|
|
250
|
+
for (const [group, keys] of Object.entries(GEM_BUTTON_TOKEN_KEYS)) {
|
|
251
|
+
const values = tokens?.[group];
|
|
252
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) continue;
|
|
253
|
+
const byNorm = new Map(keys.map((k) => [buttonKeyNorm(k), k]));
|
|
254
|
+
let copy = null;
|
|
255
|
+
for (const [rawKey, raw] of Object.entries(values)) {
|
|
256
|
+
const key = byNorm.get(buttonKeyNorm(rawKey));
|
|
257
|
+
if (!key) continue;
|
|
258
|
+
copy ??= { ...values };
|
|
259
|
+
delete copy[rawKey];
|
|
260
|
+
const v = buttonTokenValue(group, key, raw);
|
|
261
|
+
if (v === null) out.invalid.push({ path: `${group}.${rawKey}`, value: raw });
|
|
262
|
+
else {
|
|
263
|
+
copy[key] = v;
|
|
264
|
+
out.values[key] = v;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (copy) out.groups[group] = copy;
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
function splitSelectorList(selector) {
|
|
272
|
+
const parts = [];
|
|
273
|
+
let depth = 0, cur = "";
|
|
274
|
+
for (const ch of selector) {
|
|
275
|
+
if (ch === "(" || ch === "[") depth++;
|
|
276
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
277
|
+
if (ch === "," && depth === 0) {
|
|
278
|
+
parts.push(cur.trim());
|
|
279
|
+
cur = "";
|
|
280
|
+
} else cur += ch;
|
|
281
|
+
}
|
|
282
|
+
if (cur.trim()) parts.push(cur.trim());
|
|
283
|
+
return parts;
|
|
284
|
+
}
|
|
285
|
+
function gemButtonCaseCss(tokens, selector = ":root") {
|
|
286
|
+
const { values } = readButtonTokens(tokens);
|
|
287
|
+
const decls = [
|
|
288
|
+
values.buttonTransform && "text-transform: var(--gem-font-button-transform);",
|
|
289
|
+
values.buttonLetterSpacing && "letter-spacing: var(--gem-font-button-letter-spacing);"
|
|
290
|
+
].filter(Boolean);
|
|
291
|
+
if (!decls.length) return "";
|
|
292
|
+
const scope = splitSelectorList(selector).map((sel) => `${sel} .gem-button:not(.gem-button--link)`).join(", ");
|
|
293
|
+
return `:where(${scope}) { ${decls.join(" ")} }`;
|
|
294
|
+
}
|
|
228
295
|
var requiredGemTokenKeys = {
|
|
229
296
|
color: ["background", "surface", "text", "muted", "accent", "accentText", "border", "success", "warning", "danger"],
|
|
230
297
|
typography: ["body", "heading", "mono"],
|
|
@@ -237,6 +304,52 @@ var requiredGemTokenKeys = {
|
|
|
237
304
|
component: ["headerHeight", "cardPadding", "buttonPaddingInline", "buttonPaddingBlock", "inputHeight"],
|
|
238
305
|
morphology: ["campaignDensity", "mediaRhythm", "proofIntensity", "commerceEmphasis", "policyTone"]
|
|
239
306
|
};
|
|
307
|
+
var GEM_PHONE_TOKEN_TARGETS = {
|
|
308
|
+
spacing: [...requiredGemTokenKeys.spacing, ...Object.keys(GEM_DERIVED_SPACE_STEPS)],
|
|
309
|
+
layout: ["section"]
|
|
310
|
+
};
|
|
311
|
+
var tokenKeyNorm = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
312
|
+
var PHONE_TARGET_BY_KEY = Object.fromEntries(
|
|
313
|
+
Object.entries(GEM_PHONE_TOKEN_TARGETS).map(([group, targets]) => [group, new Map(targets.map((t) => [`${tokenKeyNorm(t)}mobile`, t]))])
|
|
314
|
+
);
|
|
315
|
+
function gemPhoneTokenTarget(group, key) {
|
|
316
|
+
return PHONE_TARGET_BY_KEY[group]?.get(tokenKeyNorm(key)) ?? null;
|
|
317
|
+
}
|
|
318
|
+
function readPhoneTokens(tokens) {
|
|
319
|
+
const out = { spacing: {}, layout: {}, invalid: [] };
|
|
320
|
+
for (const group of ["spacing", "layout"]) {
|
|
321
|
+
const ownGroup = group === "spacing" ? /var\(\s*--gem-space-/i : /var\(\s*--gem-layout-/i;
|
|
322
|
+
for (const [key, raw] of Object.entries(tokens?.[group] ?? {})) {
|
|
323
|
+
const target = gemPhoneTokenTarget(group, key);
|
|
324
|
+
if (target == null) continue;
|
|
325
|
+
const v = stripImportant(String(raw ?? "")).trim();
|
|
326
|
+
const kind = gemSpaceMdKind(v);
|
|
327
|
+
if (kind === "absent") continue;
|
|
328
|
+
if (kind === "zero") out[group][target] = "0px";
|
|
329
|
+
else if (kind === "length" && !ownGroup.test(v)) out[group][target] = v;
|
|
330
|
+
else out.invalid.push({ path: `${group}.${key}`, value: raw, why: kind === "length" ? `it reads a ${group} variable, which re-declared on :root is a cycle` : "not a length (a number needs a unit, e.g. 70px)" });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
var GEM_LINE_HEIGHT_TOKEN_KEYS = ["headingLineHeight", "bodyLineHeight"];
|
|
336
|
+
var LINE_HEIGHT_SHAPE = /^(normal|\d*\.?\d+(px|rem|em|%)?|(var|calc|clamp|min|max)\(.*\))$/i;
|
|
337
|
+
var isLineHeightKey = (k) => GEM_LINE_HEIGHT_TOKEN_KEYS.some((l) => tokenKeyNorm(l) === tokenKeyNorm(k));
|
|
338
|
+
var badLineHeight = (k, v) => isLineHeightKey(k) && !LINE_HEIGHT_SHAPE.test(stripImportant(String(v ?? "")).trim());
|
|
339
|
+
var desktopEntries = (group, values) => values ? Object.fromEntries(Object.entries(values).filter(([key]) => gemPhoneTokenTarget(group, key) == null)) : values;
|
|
340
|
+
var GEM_TYPE_RAMP_KEYS = ["base", "scale", "baseMobile"];
|
|
341
|
+
var optionalGemTokenKeys = {
|
|
342
|
+
typography: [...GEM_TYPE_RAMP_KEYS, ...GEM_LINE_HEIGHT_TOKEN_KEYS, ...GEM_BUTTON_TOKEN_KEYS.typography],
|
|
343
|
+
spacing: [...Object.keys(GEM_DERIVED_SPACE_STEPS), ...GEM_PHONE_TOKEN_TARGETS.spacing.map((k) => `${k}Mobile`)],
|
|
344
|
+
layout: GEM_PHONE_TOKEN_TARGETS.layout.map((k) => `${k}Mobile`),
|
|
345
|
+
radius: [...GEM_BUTTON_TOKEN_KEYS.radius]
|
|
346
|
+
};
|
|
347
|
+
var GEM_HEADING_SIZE_CSS_VARS = [
|
|
348
|
+
"--gem-section-heading-size: calc(var(--gem-h2) * var(--gem-typography-heading-scale, 1));",
|
|
349
|
+
"--gem-hero-heading-size: calc(var(--gem-h1) * var(--gem-typography-heading-scale, 1));",
|
|
350
|
+
"--gem-section-heading-size-mobile: min(var(--gem-section-heading-size), var(--gem-heading-phone-cap, clamp(1.75rem, 9vw, 2.75rem)));",
|
|
351
|
+
"--gem-hero-heading-size-mobile: min(var(--gem-hero-heading-size), var(--gem-heading-phone-cap, clamp(1.75rem, 9vw, 2.75rem)));"
|
|
352
|
+
];
|
|
240
353
|
var defaultGemTokens = {
|
|
241
354
|
color: {
|
|
242
355
|
background: "#f6f6f3",
|
|
@@ -342,23 +455,37 @@ function validateGemTokens(tokens) {
|
|
|
342
455
|
}
|
|
343
456
|
}
|
|
344
457
|
}
|
|
458
|
+
for (const bad of readButtonTokens(tokens).invalid) {
|
|
459
|
+
const shape = GEM_BUTTON_TOKEN_SHAPES[bad.path.replace(/\.[^.]+$/, (k) => "." + (Object.values(GEM_BUTTON_TOKEN_KEYS).flat().find((c) => buttonKeyNorm(c) === buttonKeyNorm(k.slice(1))) ?? k.slice(1)))];
|
|
460
|
+
issues.push(tokenIssue("warn", "invalid_button_token", `Gem token ${bad.path} = ${JSON.stringify(bad.value)} is not a valid value (${shape}); the button keeps its default.`, bad.path));
|
|
461
|
+
}
|
|
462
|
+
for (const bad of readPhoneTokens(tokens).invalid) {
|
|
463
|
+
issues.push(tokenIssue("warn", "invalid_phone_token", `Gem token ${bad.path} = ${JSON.stringify(bad.value)} is not used: ${bad.why}. Phones keep the desktop value.`, bad.path));
|
|
464
|
+
}
|
|
465
|
+
for (const [key, value] of Object.entries(tokens.typography ?? {})) {
|
|
466
|
+
if (badLineHeight(key, value)) issues.push(tokenIssue("warn", "invalid_line_height_token", `Gem token typography.${key} = ${JSON.stringify(value)} is not a line-height (1.2, 120%, 24px, normal); headings keep their default.`, `typography.${key}`));
|
|
467
|
+
}
|
|
345
468
|
return {
|
|
346
469
|
valid: !issues.some((issue) => issue.severity === "error"),
|
|
347
470
|
issues
|
|
348
471
|
};
|
|
349
472
|
}
|
|
350
473
|
function createGemTokenCss(tokens, selector = ":root") {
|
|
474
|
+
const button = readButtonTokens(tokens);
|
|
475
|
+
const phone = readPhoneTokens(tokens);
|
|
476
|
+
const spacing = desktopEntries("spacing", tokens.spacing);
|
|
351
477
|
const declarations = [
|
|
352
478
|
...toCssVars("color", tokens.color),
|
|
353
|
-
...toCssVars("font", Object.fromEntries(Object.entries(tokens.typography ?? {}).filter(([k]) => !isRampKey(k)))),
|
|
479
|
+
...toCssVars("font", Object.fromEntries(Object.entries(button.groups.typography ?? tokens.typography ?? {}).filter(([k, v]) => !isRampKey(k) && !badLineHeight(k, v)))),
|
|
354
480
|
// Space ramp is scaled by the global density knob --gem-style-space (default 1),
|
|
355
481
|
// so a style pack can make the WHOLE storefront airier or denser without touching
|
|
356
482
|
// any section — every section already consumes --gem-space-*. Knob unset → identical.
|
|
357
|
-
|
|
358
|
-
...
|
|
483
|
+
// Phone keys (`3xlMobile`) are not desktop variables: they go to the phone block below (GEM_PHONE_TOKEN_TARGETS).
|
|
484
|
+
...toScaledCssVars("space", spacing, "--gem-style-space"),
|
|
485
|
+
...gemDerivedSpaceCssVars(spacing),
|
|
359
486
|
// Radius ramp scaled by --gem-style-radius (default 1): a 'sharp' style sets it to
|
|
360
487
|
// 0 (square everything), 'pill' rounds further — across every section, no edits.
|
|
361
|
-
...toScaledCssVars("radius", tokens.radius, "--gem-style-radius"),
|
|
488
|
+
...toScaledCssVars("radius", button.groups.radius ?? tokens.radius, "--gem-style-radius"),
|
|
362
489
|
// Border widths get the --gem-style-border knob injected into their leading length,
|
|
363
490
|
// whatever value the plan provides (e.g. "1px solid …" → "calc(1px * var(...)) solid …").
|
|
364
491
|
...toBorderCssVars("border", tokens.border, "--gem-style-border"),
|
|
@@ -367,7 +494,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
367
494
|
// and marquee primitives consume; unrecognised keys fall back to the --gem-motion-* namespace,
|
|
368
495
|
// so duration/easing keep emitting --gem-motion-duration/easing exactly as before (back-compat).
|
|
369
496
|
...toMotionCssVars(tokens.motion),
|
|
370
|
-
...toCssVars("layout", tokens.layout),
|
|
497
|
+
...toCssVars("layout", desktopEntries("layout", tokens.layout)),
|
|
498
|
+
...phone.layout.section ? [`--gem-layout-section-mobile: ${phone.layout.section};`] : [],
|
|
371
499
|
...toCssVars("component", tokens.component),
|
|
372
500
|
...toCssVars("morphology", tokens.morphology),
|
|
373
501
|
// THE GOVERNED TYPE RAMP — --gem-h1..h6 / --gem-body / --gem-text-sm.
|
|
@@ -377,6 +505,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
377
505
|
// variables that resolved to nothing. One modular scale, retuned by overriding --gem-type-base
|
|
378
506
|
// or --gem-type-scale (see gemTypeRampCssVars).
|
|
379
507
|
...gemTypeRampCssVars(tokens.typography),
|
|
508
|
+
// Heading sizes from the ramp, only for a plan that chose a ramp (GEM_HEADING_SIZE_CSS_VARS).
|
|
509
|
+
...typeRampValue(tokens.typography, "base") || typeRampValue(tokens.typography, "scale") ? GEM_HEADING_SIZE_CSS_VARS : [],
|
|
380
510
|
// Focus ring (gemfactory request #3, 28/09/2026): 11 sections wrote `outline: 2px solid
|
|
381
511
|
// var(--gem-color-accent)` by hand. Not a token GROUP (a group would name it --gem-border-focus-ring
|
|
382
512
|
// and a required key would fail every stored plan/preset without it), so it is emitted here like the
|
|
@@ -386,16 +516,25 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
386
516
|
GEM_FOCUS_OFFSET_CSS_VAR
|
|
387
517
|
];
|
|
388
518
|
const formBlock = gemFormCssBlock(tokens.form);
|
|
389
|
-
const
|
|
519
|
+
const buttonCase = gemButtonCaseCss(tokens, selector);
|
|
520
|
+
const mobile = gemMobileTokenCssVars(tokens, phone);
|
|
390
521
|
return [
|
|
391
522
|
`${selector} {`,
|
|
392
523
|
...declarations.map((line) => ` ${line}`),
|
|
393
524
|
"}",
|
|
394
|
-
...
|
|
395
|
-
...formBlock ? [formBlock] : []
|
|
525
|
+
...mobile.length ? [`@media (max-width: ${GEM_TYPE_MOBILE_MAX}) {`, ` ${selector} {`, ...mobile.map((line) => ` ${line}`), " }", "}"] : [],
|
|
526
|
+
...formBlock ? [formBlock] : [],
|
|
527
|
+
...buttonCase ? [buttonCase] : []
|
|
396
528
|
].join("\n");
|
|
397
529
|
}
|
|
398
|
-
|
|
530
|
+
function gemMobileTokenCssVars(tokens, phone = readPhoneTokens(tokens)) {
|
|
531
|
+
const baseMobile = typeRampValue(tokens.typography, "baseMobile");
|
|
532
|
+
return [
|
|
533
|
+
...baseMobile ? [`--gem-type-base: ${baseMobile};`] : [],
|
|
534
|
+
...toScaledCssVars("space", phone.spacing, "--gem-style-space"),
|
|
535
|
+
...toCssVars("layout", phone.layout)
|
|
536
|
+
];
|
|
537
|
+
}
|
|
399
538
|
var rampKey = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
400
539
|
var isRampKey = (k) => GEM_TYPE_RAMP_KEYS.some((r) => rampKey(r) === rampKey(k));
|
|
401
540
|
var LENGTH = /^\d*\.?\d+(px|rem|em)$/;
|
|
@@ -879,10 +1018,10 @@ function createGemFrameworkManifest() {
|
|
|
879
1018
|
package_name: "@gemtheme/gem-framework",
|
|
880
1019
|
framework_version: gemFrameworkVersion,
|
|
881
1020
|
namespace: "gem",
|
|
882
|
-
token_groups: Object.entries(requiredGemTokenKeys).map(([group, keys]) =>
|
|
883
|
-
group
|
|
884
|
-
keys: [...keys]
|
|
885
|
-
})
|
|
1021
|
+
token_groups: Object.entries(requiredGemTokenKeys).map(([group, keys]) => {
|
|
1022
|
+
const optional = [...optionalGemTokenKeys[group] ?? []];
|
|
1023
|
+
return { group, keys: [...keys], ...optional.length ? { optional_keys: optional } : {} };
|
|
1024
|
+
}),
|
|
886
1025
|
primitive_groups: gemFrameworkPrimitiveGroups.map((group) => ({
|
|
887
1026
|
group: group.group,
|
|
888
1027
|
classes: [...group.classes]
|
|
@@ -1186,7 +1325,6 @@ var GEM_AMBIENT_CSS_LINES = [
|
|
|
1186
1325
|
"@media (prefers-reduced-motion: reduce) {",
|
|
1187
1326
|
" [data-gem-ambient] { --gem-ambient-amp: 2px; animation-duration: 12s; }",
|
|
1188
1327
|
" [data-gem-reveal] { transition: none; opacity: 1; transform: none; }",
|
|
1189
|
-
" .gem-marquee__track { animation-duration: calc(var(--gem-marquee-duration, 40s) * 2.5); }",
|
|
1190
1328
|
"}"
|
|
1191
1329
|
];
|
|
1192
1330
|
var GEM_AMBIENT_CSS = GEM_AMBIENT_CSS_LINES.join("\n");
|
|
@@ -1376,9 +1514,13 @@ function createPrimitiveCss() {
|
|
|
1376
1514
|
" justify-content: center;",
|
|
1377
1515
|
" min-height: var(--gem-component-input-height, 44px);",
|
|
1378
1516
|
" border: var(--gem-button-border-width, 1px) solid var(--gem-color-border);",
|
|
1379
|
-
|
|
1517
|
+
// Button tokens (GEM_BUTTON_TOKEN_KEYS, optional): unset, radius and weight fall back to exactly what this rule always
|
|
1518
|
+
// drew. --gem-radius-button is the button's own radius, so a pill button does not turn every card into a pill
|
|
1519
|
+
// (radius-md is shared). Case and tracking are NOT declared here: <button> gets `none` / `normal` from the UA sheet
|
|
1520
|
+
// while <a> inherits, so createGemTokenCss adds them only when a token sets them (gemButtonCaseCss).
|
|
1521
|
+
" border-radius: var(--gem-radius-button, var(--gem-radius-md));",
|
|
1380
1522
|
" padding: var(--gem-component-button-padding-block, 12px) var(--gem-component-button-padding-inline, 18px);",
|
|
1381
|
-
" font-weight: 700;",
|
|
1523
|
+
" font-weight: var(--gem-font-button-weight, 700);",
|
|
1382
1524
|
" text-decoration: none;",
|
|
1383
1525
|
" transition: background var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease), color var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease);",
|
|
1384
1526
|
"}",
|
|
@@ -1641,28 +1783,6 @@ function createPrimitiveCss() {
|
|
|
1641
1783
|
" border-radius: var(--gem-radius-md);",
|
|
1642
1784
|
" background: color-mix(in srgb, var(--gem-color-text) 8%, var(--gem-color-background));",
|
|
1643
1785
|
"}",
|
|
1644
|
-
".gem-marquee {",
|
|
1645
|
-
" overflow: hidden;",
|
|
1646
|
-
" border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1647
|
-
"}",
|
|
1648
|
-
// Consumer contract: render the track's children TWICE (like the site's `.mq-row`) so the
|
|
1649
|
-
// -50% translate lands on an identical second copy and the loop is seamless with no visible jump.
|
|
1650
|
-
// The animation reuses the gem-marquee-scroll keyframe defined with the ambient primitive.
|
|
1651
|
-
".gem-marquee__track {",
|
|
1652
|
-
" display: flex;",
|
|
1653
|
-
" gap: var(--gem-space-lg);",
|
|
1654
|
-
" min-width: max-content;",
|
|
1655
|
-
" animation: gem-marquee-scroll var(--gem-marquee-duration, 40s) linear infinite;",
|
|
1656
|
-
" will-change: transform;",
|
|
1657
|
-
"}",
|
|
1658
|
-
// Pause-on-hover lets a shopper read a badge/logo they want to inspect.
|
|
1659
|
-
".gem-marquee:hover .gem-marquee__track {",
|
|
1660
|
-
" animation-play-state: paused;",
|
|
1661
|
-
"}",
|
|
1662
|
-
// Opt-in reverse direction (e.g. a second row scrolling the other way).
|
|
1663
|
-
".gem-marquee[data-gem-marquee-reverse] .gem-marquee__track {",
|
|
1664
|
-
" animation-direction: reverse;",
|
|
1665
|
-
"}",
|
|
1666
1786
|
".gem-carousel {",
|
|
1667
1787
|
" display: grid;",
|
|
1668
1788
|
" gap: var(--gem-space-md);",
|
|
@@ -1798,9 +1918,20 @@ function createPrimitiveCss() {
|
|
|
1798
1918
|
" overflow: hidden;",
|
|
1799
1919
|
" color: var(--gem-color-accent);",
|
|
1800
1920
|
"}",
|
|
1801
|
-
// Marquee primitive
|
|
1802
|
-
//
|
|
1803
|
-
|
|
1921
|
+
// Marquee primitive. CSS-only and deliberately so (no JS to init, works on every render path).
|
|
1922
|
+
// Contract: `.gem-marquee > .gem-marquee__viewport > .gem-marquee__track` TWICE (the second copy
|
|
1923
|
+
// aria-hidden); each track is at least the viewport wide and moves -100% of itself, so the second
|
|
1924
|
+
// copy is what makes the loop seamless. gemcapture's clone kit and GemFactory build exactly this.
|
|
1925
|
+
// ONE definition (0.9.11): until then a second, earlier one (gemtheme #124: one track with its
|
|
1926
|
+
// children doubled, -50%) sat above this and lost every property both set, so its contract never
|
|
1927
|
+
// worked. What it set that still took effect (the border, will-change, pause on hover of the whole
|
|
1928
|
+
// marquee, the data-attribute reverse) is kept here, so a marquee built to either contract computes
|
|
1929
|
+
// the same style as before (Chromium, 08/10/2026). The one exception would be a track that also
|
|
1930
|
+
// carries data-gem-ambient, which nothing renders.
|
|
1931
|
+
".gem-marquee {",
|
|
1932
|
+
" overflow: hidden;",
|
|
1933
|
+
" border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1934
|
+
"}",
|
|
1804
1935
|
".gem-marquee__viewport { display: flex; overflow: hidden; }",
|
|
1805
1936
|
".gem-marquee__track {",
|
|
1806
1937
|
" flex: 0 0 auto;",
|
|
@@ -1811,9 +1942,12 @@ function createPrimitiveCss() {
|
|
|
1811
1942
|
" gap: var(--gem-space-lg);",
|
|
1812
1943
|
" white-space: nowrap;",
|
|
1813
1944
|
" animation: gem-marquee var(--gem-marquee-duration, 24s) linear infinite;",
|
|
1945
|
+
" will-change: transform;",
|
|
1814
1946
|
"}",
|
|
1815
|
-
|
|
1816
|
-
".gem-marquee
|
|
1947
|
+
// Pause-on-hover lets a shopper read a badge/logo they want to inspect.
|
|
1948
|
+
".gem-marquee:hover .gem-marquee__track, .gem-marquee__viewport:hover .gem-marquee__track { animation-play-state: paused; }",
|
|
1949
|
+
// Opt-in reverse direction (e.g. a second row scrolling the other way).
|
|
1950
|
+
".gem-marquee--reverse .gem-marquee__track, .gem-marquee[data-gem-marquee-reverse] .gem-marquee__track { animation-direction: reverse; }",
|
|
1817
1951
|
"@keyframes gem-marquee { from { transform: translateX(0); } to { transform: translateX(-100%); } }",
|
|
1818
1952
|
"@media (prefers-reduced-motion: reduce) {",
|
|
1819
1953
|
" .gem-marquee__track { animation: none; }",
|
|
@@ -2831,10 +2965,10 @@ function createGemSectionRuntimeContractCss(selector = "[data-gem-section-librar
|
|
|
2831
2965
|
"}"
|
|
2832
2966
|
].join("\n");
|
|
2833
2967
|
}
|
|
2834
|
-
var OPT_IN_EYEBROW = ":where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; letter-spacing: 0.12em; }";
|
|
2835
|
-
var OPT_IN_HEADING = ":where(.gem-section[data-gem-heading='section']) .gem-section__heading { margin: 0; font-family: var(--gem-font-heading); font-size: var(--gem-h2); line-height: 1.1; letter-spacing: -0.01em; text-wrap: balance; }";
|
|
2968
|
+
var OPT_IN_EYEBROW = ":where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; font-size: var(--gem-text-sm); letter-spacing: 0.12em; }";
|
|
2969
|
+
var OPT_IN_HEADING = ":where(.gem-section[data-gem-heading='section']) .gem-section__heading { margin: 0; font-family: var(--gem-font-heading); font-size: var(--gem-section-heading-size, var(--gem-h2)); line-height: var(--gem-font-heading-line-height, 1.1); letter-spacing: -0.01em; text-wrap: balance; }";
|
|
2836
2970
|
var OPT_IN_BODY = ":where(.gem-section[data-gem-body='section']) .gem-section__body { margin: 0; color: var(--gem-color-muted); font-size: var(--gem-body); line-height: var(--gem-font-body-line-height, 1.5); }";
|
|
2837
|
-
var OPT_IN_HEADING_MOBILE = " :where(.gem-section[data-gem-heading='section']) .gem-section__heading { font-size: min(var(--gem-h2), clamp(1.75rem, 9vw, 2.75rem)); }";
|
|
2971
|
+
var OPT_IN_HEADING_MOBILE = " :where(.gem-section[data-gem-heading='section']) .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, min(var(--gem-h2), clamp(1.75rem, 9vw, 2.75rem))); }";
|
|
2838
2972
|
function createSectionCss() {
|
|
2839
2973
|
return [
|
|
2840
2974
|
".gem-section {",
|
|
@@ -3026,8 +3160,8 @@ function createSectionCss() {
|
|
|
3026
3160
|
".gem-section__heading {",
|
|
3027
3161
|
" margin: 0;",
|
|
3028
3162
|
" font-family: var(--gem-font-heading);",
|
|
3029
|
-
" font-size: clamp(calc(2rem * var(--gem-typography-heading-scale, 1)), calc(4vw * var(--gem-typography-heading-scale, 1)), calc(4rem * var(--gem-typography-heading-scale, 1)));",
|
|
3030
|
-
" line-height: 1.04;",
|
|
3163
|
+
" font-size: var(--gem-section-heading-size, clamp(calc(2rem * var(--gem-typography-heading-scale, 1)), calc(4vw * var(--gem-typography-heading-scale, 1)), calc(4rem * var(--gem-typography-heading-scale, 1))));",
|
|
3164
|
+
" line-height: var(--gem-font-heading-line-height, 1.04);",
|
|
3031
3165
|
"}",
|
|
3032
3166
|
".gem-section__body {",
|
|
3033
3167
|
" margin: 0;",
|
|
@@ -3144,9 +3278,10 @@ function createSectionCss() {
|
|
|
3144
3278
|
" }",
|
|
3145
3279
|
"}",
|
|
3146
3280
|
"@media (max-width: 760px) {",
|
|
3147
|
-
|
|
3281
|
+
// A declared layout.sectionMobile is final (tokens.ts GEM_PHONE_TOKEN_TARGETS); otherwise the desktop value × 0.72.
|
|
3282
|
+
" .gem-section { padding-block: var(--gem-layout-section-mobile, calc(var(--gem-layout-section, 64px) * 0.72)); }",
|
|
3148
3283
|
" .gem-section__inner, .gem-container { width: min(100% - 32px, var(--gem-layout-page, 1180px)); }",
|
|
3149
|
-
" .gem-section__heading { font-size: clamp(1.75rem, 9vw, 2.75rem); }",
|
|
3284
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, clamp(1.75rem, 9vw, 2.75rem)); }",
|
|
3150
3285
|
" .gem-section__grid { grid-template-columns: minmax(0, 1fr); }",
|
|
3151
3286
|
" .gem-section:is([data-gem-composition-mode='split-collage'], [data-gem-editorial-band-mode='media-story']) .gem-section__grid { grid-template-columns: minmax(0, 1fr); }",
|
|
3152
3287
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-main__inner { grid-template-columns: minmax(0, 1fr); }",
|
|
@@ -3166,7 +3301,7 @@ function createSectionCss() {
|
|
|
3166
3301
|
function createDawnParityCss() {
|
|
3167
3302
|
return [
|
|
3168
3303
|
"/* Dawn parity profile: locked default baseline; morph presets must opt out before applying Gem visual identity. */",
|
|
3169
|
-
":root { --gem-profile-mode: dawn-parity; --gem-layout-page: 1100px; --gem-layout-gutter: 32px; --gem-layout-section: 52px; --gem-radius-md: 0px; --gem-card-radius: 0px; --gem-media-radius: 0px; --gem-grid-column-gap: 8px; --gem-grid-row-gap: 8px; }",
|
|
3304
|
+
":root { --gem-profile-mode: dawn-parity; --gem-layout-page: 1100px; --gem-layout-gutter: 32px; --gem-layout-section: 52px; --gem-radius-md: 0px; --gem-card-radius: 0px; --gem-media-radius: 0px; --gem-grid-column-gap: 8px; --gem-grid-row-gap: 8px; --gem-heading-phone-cap: clamp(2rem, 11vw, 3.6rem); }",
|
|
3170
3305
|
".utility-bar, .announcement-bar, .utility-bar__grid { min-height: 38px; }",
|
|
3171
3306
|
".announcement-bar__message { margin: 0; min-height: 38px; padding-block: 10px; font-size: 0.75rem; font-weight: 400; letter-spacing: 0.04em; }",
|
|
3172
3307
|
// The pair of rules here used to blank the real message (font-size: 0) and inject a FIXED
|
|
@@ -3242,7 +3377,12 @@ function createDawnParityCss() {
|
|
|
3242
3377
|
".gem-hero-banner--split-proof .gem-hero-banner__media, .gem-hero-banner--editorial .gem-hero-banner__media { position: relative; inset: auto; min-height: 26rem; height: 100%; overflow: hidden; border-radius: 6px; background: rgb(244 244 244); }",
|
|
3243
3378
|
".gem-hero-banner--split-proof .gem-hero-banner__image, .gem-hero-banner--split-proof .gem-hero-banner__placeholder, .gem-hero-banner--editorial .gem-hero-banner__image, .gem-hero-banner--editorial .gem-hero-banner__placeholder { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }",
|
|
3244
3379
|
".gem-hero-banner .gem-section__content { max-width: 78rem; margin-inline: auto; }",
|
|
3245
|
-
".gem-hero-banner h1, .gem-hero-banner h2, .gem-section__heading { margin: 0 0 1.5rem; font-
|
|
3380
|
+
".gem-hero-banner h1, .gem-hero-banner h2, .gem-section__heading { margin: 0 0 1.5rem; font-weight: 400; line-height: var(--gem-font-heading-line-height, 1.08); letter-spacing: 0; }",
|
|
3381
|
+
// Font size has ONE owner per selector (one-owner-per-selector.test.ts): section headings take H2 of the plan's ramp,
|
|
3382
|
+
// the hero's own title H1 (tokens.ts GEM_HEADING_SIZE_CSS_VARS). Same position and specificity as the shared rule above,
|
|
3383
|
+
// so with no ramp both resolve to the clamp that rule used to set.
|
|
3384
|
+
".gem-section__heading { font-size: var(--gem-section-heading-size, clamp(calc(2.5rem * var(--gem-typography-heading-scale, 1)), calc(4vw * var(--gem-typography-heading-scale, 1)), calc(5.2rem * var(--gem-typography-heading-scale, 1)))); }",
|
|
3385
|
+
".gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size, clamp(calc(2.5rem * var(--gem-typography-heading-scale, 1)), calc(4vw * var(--gem-typography-heading-scale, 1)), calc(5.2rem * var(--gem-typography-heading-scale, 1)))); }",
|
|
3246
3386
|
".gem-section__eyebrow { display: none; }",
|
|
3247
3387
|
".gem-hero-banner--compact-collection { min-height: auto; display: block; text-align: left; padding-block: 25px; }",
|
|
3248
3388
|
".gem-hero-banner--compact-collection .gem-section__content, .gem-product-card-grid--quick-view-grid > .gem-section__inner { width: min(var(--gem-layout-page, 1200px), calc(100% - var(--gem-layout-gutter, 32px))); margin-inline: auto; }",
|
|
@@ -3257,7 +3397,13 @@ function createDawnParityCss() {
|
|
|
3257
3397
|
".gem-rich-text--dawn-intro .gem-section__body { width: 100%; max-width: none; margin: 20px 0 0; font-size: 16px; line-height: 28.8px; }",
|
|
3258
3398
|
".gem-rich-text--dawn-intro .gem-section__body p { margin: 0; }",
|
|
3259
3399
|
".gem-rich-text--dawn-intro .gem-rich-text__blocks { display: block; width: 100%; }",
|
|
3260
|
-
|
|
3400
|
+
// Dawn's square, regular-weight button stays the default; the button tokens (radius.button, typography.buttonWeight)
|
|
3401
|
+
// override it, or this rule (same specificity, later) would pin every button square at 400 whatever the plan says.
|
|
3402
|
+
// The weight sits in its own rule that skips link buttons, which keep today's 400 here: the rule above it is what
|
|
3403
|
+
// overrides `.gem-button--link { font-weight: inherit }` in this profile, and a weight token must not reach a link.
|
|
3404
|
+
// `:where()` keeps both at one class of specificity, so a section's own CTA rule still wins.
|
|
3405
|
+
".gem-button { min-width: 12rem; border-radius: var(--gem-radius-button, 0); font-weight: 400; box-shadow: none; }",
|
|
3406
|
+
".gem-button:where(:not(.gem-button--link)) { font-weight: var(--gem-font-button-weight, 400); }",
|
|
3261
3407
|
".gem-product-card-wrapper { display: block; min-width: 0; }",
|
|
3262
3408
|
".gem-card, .gem-product-card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }",
|
|
3263
3409
|
".gem-product-card__media, .gem-media { border: 0; border-radius: 0; box-shadow: none; background: rgb(244 244 244); }",
|
|
@@ -3453,7 +3599,9 @@ function createDawnParityMobileCss() {
|
|
|
3453
3599
|
" .gem-product-card__content { padding-top: 8px; gap: 4px; }",
|
|
3454
3600
|
" .gem-product-card__title { font-size: 15px; line-height: 24px; }",
|
|
3455
3601
|
" .gem-product-card__price, .gem-product-card__price .gem-price { font-size: 16px; line-height: 24px; }",
|
|
3456
|
-
|
|
3602
|
+
// Fallback = the profile's own phone heading, declared once as --gem-heading-phone-cap on the :root above.
|
|
3603
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3604
|
+
" .gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3457
3605
|
" .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)); }",
|
|
3458
3606
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] > .gem-section__inner { width: calc(100% - 30px); }",
|
|
3459
3607
|
" .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; }",
|
|
@@ -5727,7 +5875,10 @@ function gemFrameworkPublicCssVariables() {
|
|
|
5727
5875
|
createPrimitiveCss(),
|
|
5728
5876
|
String(createSectionCss()),
|
|
5729
5877
|
String(createShellCss()),
|
|
5730
|
-
String(createGemSectionRuntimeContractCss())
|
|
5878
|
+
String(createGemSectionRuntimeContractCss()),
|
|
5879
|
+
// The button case rule only exists when a token sets it; a section may still read its variables (a CTA rule
|
|
5880
|
+
// beats the zero-specificity rule, so opting in means `text-transform: var(--gem-font-button-transform, none)`).
|
|
5881
|
+
gemButtonCaseCss({ typography: { buttonTransform: "none", buttonLetterSpacing: "normal" } })
|
|
5731
5882
|
].join("\n").replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
5732
5883
|
frameworkPublicVariables = gemPublicCssVariablesFrom(css);
|
|
5733
5884
|
}
|
|
@@ -6898,6 +7049,8 @@ function stringSetting(value) {
|
|
|
6898
7049
|
export {
|
|
6899
7050
|
COUNTDOWN_END_RUNTIME_LINES,
|
|
6900
7051
|
GEM_AMBIENT_CSS,
|
|
7052
|
+
GEM_BUTTON_TOKEN_KEYS,
|
|
7053
|
+
GEM_BUTTON_TOKEN_SHAPES,
|
|
6901
7054
|
GEM_COUNTDOWN_ENDED_CSS,
|
|
6902
7055
|
GEM_FIELD_JOINS,
|
|
6903
7056
|
GEM_FIELD_SHAPES,
|
|
@@ -6908,6 +7061,8 @@ export {
|
|
|
6908
7061
|
GEM_FORM_TOKEN_KEYS,
|
|
6909
7062
|
GEM_FORM_VAR_SCOPE,
|
|
6910
7063
|
GEM_GRID_COLUMNS_ROW_KNOB_CSS,
|
|
7064
|
+
GEM_HEADING_SIZE_CSS_VARS,
|
|
7065
|
+
GEM_LINE_HEIGHT_TOKEN_KEYS,
|
|
6911
7066
|
GEM_PART_CLASS,
|
|
6912
7067
|
GEM_POPUP_SHELL_CSS,
|
|
6913
7068
|
GEM_REVEAL_CSS,
|
|
@@ -6939,6 +7094,7 @@ export {
|
|
|
6939
7094
|
createThemeProfileCss,
|
|
6940
7095
|
defaultGemTokens,
|
|
6941
7096
|
defaultSelection,
|
|
7097
|
+
gemButtonCaseCss,
|
|
6942
7098
|
gemFormAttributes,
|
|
6943
7099
|
gemFormCssBlock,
|
|
6944
7100
|
gemFormCssVars,
|
|
@@ -6947,7 +7103,9 @@ export {
|
|
|
6947
7103
|
gemFrameworkRuntimePlugins,
|
|
6948
7104
|
gemFrameworkValidationRules,
|
|
6949
7105
|
gemFrameworkVersion,
|
|
7106
|
+
gemMobileTokenCssVars,
|
|
6950
7107
|
gemPartClass,
|
|
7108
|
+
gemPhoneTokenTarget,
|
|
6951
7109
|
gemPlanShapeKnobs,
|
|
6952
7110
|
gemRuntimePluginModuleKeys,
|
|
6953
7111
|
gemRuntimePluginSources,
|
|
@@ -6958,6 +7116,7 @@ export {
|
|
|
6958
7116
|
motionCssVarAliases,
|
|
6959
7117
|
normalizeGemTokens,
|
|
6960
7118
|
offerEngineRuntimeScript,
|
|
7119
|
+
optionalGemTokenKeys,
|
|
6961
7120
|
planToRuntimeBootstrapConfig,
|
|
6962
7121
|
renderGemFrameworkManifest,
|
|
6963
7122
|
renderGemPreviewDocument,
|
package/dist/primitives.css
CHANGED
|
@@ -93,9 +93,9 @@ a {
|
|
|
93
93
|
justify-content: center;
|
|
94
94
|
min-height: var(--gem-component-input-height, 44px);
|
|
95
95
|
border: var(--gem-button-border-width, 1px) solid var(--gem-color-border);
|
|
96
|
-
border-radius: var(--gem-radius-md);
|
|
96
|
+
border-radius: var(--gem-radius-button, var(--gem-radius-md));
|
|
97
97
|
padding: var(--gem-component-button-padding-block, 12px) var(--gem-component-button-padding-inline, 18px);
|
|
98
|
-
font-weight: 700;
|
|
98
|
+
font-weight: var(--gem-font-button-weight, 700);
|
|
99
99
|
text-decoration: none;
|
|
100
100
|
transition: background var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease), color var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease);
|
|
101
101
|
}
|
|
@@ -373,23 +373,6 @@ a {
|
|
|
373
373
|
border-radius: var(--gem-radius-md);
|
|
374
374
|
background: color-mix(in srgb, var(--gem-color-text) 8%, var(--gem-color-background));
|
|
375
375
|
}
|
|
376
|
-
.gem-marquee {
|
|
377
|
-
overflow: hidden;
|
|
378
|
-
border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));
|
|
379
|
-
}
|
|
380
|
-
.gem-marquee__track {
|
|
381
|
-
display: flex;
|
|
382
|
-
gap: var(--gem-space-lg);
|
|
383
|
-
min-width: max-content;
|
|
384
|
-
animation: gem-marquee-scroll var(--gem-marquee-duration, 40s) linear infinite;
|
|
385
|
-
will-change: transform;
|
|
386
|
-
}
|
|
387
|
-
.gem-marquee:hover .gem-marquee__track {
|
|
388
|
-
animation-play-state: paused;
|
|
389
|
-
}
|
|
390
|
-
.gem-marquee[data-gem-marquee-reverse] .gem-marquee__track {
|
|
391
|
-
animation-direction: reverse;
|
|
392
|
-
}
|
|
393
376
|
.gem-carousel {
|
|
394
377
|
display: grid;
|
|
395
378
|
gap: var(--gem-space-md);
|
|
@@ -515,7 +498,10 @@ a {
|
|
|
515
498
|
overflow: hidden;
|
|
516
499
|
color: var(--gem-color-accent);
|
|
517
500
|
}
|
|
518
|
-
.gem-marquee {
|
|
501
|
+
.gem-marquee {
|
|
502
|
+
overflow: hidden;
|
|
503
|
+
border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));
|
|
504
|
+
}
|
|
519
505
|
.gem-marquee__viewport { display: flex; overflow: hidden; }
|
|
520
506
|
.gem-marquee__track {
|
|
521
507
|
flex: 0 0 auto;
|
|
@@ -526,9 +512,10 @@ a {
|
|
|
526
512
|
gap: var(--gem-space-lg);
|
|
527
513
|
white-space: nowrap;
|
|
528
514
|
animation: gem-marquee var(--gem-marquee-duration, 24s) linear infinite;
|
|
515
|
+
will-change: transform;
|
|
529
516
|
}
|
|
530
|
-
.gem-marquee__viewport:hover .gem-marquee__track { animation-play-state: paused; }
|
|
531
|
-
.gem-marquee--reverse .gem-marquee__track { animation-direction: reverse; }
|
|
517
|
+
.gem-marquee:hover .gem-marquee__track, .gem-marquee__viewport:hover .gem-marquee__track { animation-play-state: paused; }
|
|
518
|
+
.gem-marquee--reverse .gem-marquee__track, .gem-marquee[data-gem-marquee-reverse] .gem-marquee__track { animation-direction: reverse; }
|
|
532
519
|
@keyframes gem-marquee { from { transform: translateX(0); } to { transform: translateX(-100%); } }
|
|
533
520
|
@media (prefers-reduced-motion: reduce) {
|
|
534
521
|
.gem-marquee__track { animation: none; }
|
|
@@ -1042,7 +1029,6 @@ a {
|
|
|
1042
1029
|
@media (prefers-reduced-motion: reduce) {
|
|
1043
1030
|
[data-gem-ambient] { --gem-ambient-amp: 2px; animation-duration: 12s; }
|
|
1044
1031
|
[data-gem-reveal] { transition: none; opacity: 1; transform: none; }
|
|
1045
|
-
.gem-marquee__track { animation-duration: calc(var(--gem-marquee-duration, 40s) * 2.5); }
|
|
1046
1032
|
}
|
|
1047
1033
|
: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); }
|
|
1048
1034
|
: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%; }
|