@gemboss/gem-framework 0.9.8 → 0.10.1
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 +74 -0
- package/dist/SOURCE.json +3 -3
- package/dist/gem-framework.css +28 -38
- package/dist/index.js +219 -57
- package/dist/primitives.css +9 -23
- package/dist/section-gate.js +196 -52
- package/dist/sections.css +18 -14
- package/dist/tokens.css +1 -1
- 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.1
|
|
194
|
+
var gemFrameworkVersion = "0.10.1";
|
|
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",
|
|
@@ -250,7 +363,10 @@ var defaultGemTokens = {
|
|
|
250
363
|
accentText: "#ffffff",
|
|
251
364
|
border: "#d2d5d8",
|
|
252
365
|
success: "#008060",
|
|
253
|
-
|
|
366
|
+
// AA as TEXT on both defaults (gem-framework 0.10.1): #b7791f measured 3.36:1 on the background and 3.64:1 on white, and
|
|
367
|
+
// GemFactory sections set `color: var(--gem-color-warning)` on words; white text on a warning badge was 3.64:1 too.
|
|
368
|
+
// #9a661a keeps the hue at 4.52:1 / 4.90:1. default-colours-meet-aa.test.ts holds every foreground default to 4.5:1.
|
|
369
|
+
warning: "#9a661a",
|
|
254
370
|
danger: "#d72c0d"
|
|
255
371
|
},
|
|
256
372
|
typography: {
|
|
@@ -342,23 +458,37 @@ function validateGemTokens(tokens) {
|
|
|
342
458
|
}
|
|
343
459
|
}
|
|
344
460
|
}
|
|
461
|
+
for (const bad of readButtonTokens(tokens).invalid) {
|
|
462
|
+
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)))];
|
|
463
|
+
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));
|
|
464
|
+
}
|
|
465
|
+
for (const bad of readPhoneTokens(tokens).invalid) {
|
|
466
|
+
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));
|
|
467
|
+
}
|
|
468
|
+
for (const [key, value] of Object.entries(tokens.typography ?? {})) {
|
|
469
|
+
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}`));
|
|
470
|
+
}
|
|
345
471
|
return {
|
|
346
472
|
valid: !issues.some((issue) => issue.severity === "error"),
|
|
347
473
|
issues
|
|
348
474
|
};
|
|
349
475
|
}
|
|
350
476
|
function createGemTokenCss(tokens, selector = ":root") {
|
|
477
|
+
const button = readButtonTokens(tokens);
|
|
478
|
+
const phone = readPhoneTokens(tokens);
|
|
479
|
+
const spacing = desktopEntries("spacing", tokens.spacing);
|
|
351
480
|
const declarations = [
|
|
352
481
|
...toCssVars("color", tokens.color),
|
|
353
|
-
...toCssVars("font", Object.fromEntries(Object.entries(tokens.typography ?? {}).filter(([k]) => !isRampKey(k)))),
|
|
482
|
+
...toCssVars("font", Object.fromEntries(Object.entries(button.groups.typography ?? tokens.typography ?? {}).filter(([k, v]) => !isRampKey(k) && !badLineHeight(k, v)))),
|
|
354
483
|
// Space ramp is scaled by the global density knob --gem-style-space (default 1),
|
|
355
484
|
// so a style pack can make the WHOLE storefront airier or denser without touching
|
|
356
485
|
// any section — every section already consumes --gem-space-*. Knob unset → identical.
|
|
357
|
-
|
|
358
|
-
...
|
|
486
|
+
// Phone keys (`3xlMobile`) are not desktop variables: they go to the phone block below (GEM_PHONE_TOKEN_TARGETS).
|
|
487
|
+
...toScaledCssVars("space", spacing, "--gem-style-space"),
|
|
488
|
+
...gemDerivedSpaceCssVars(spacing),
|
|
359
489
|
// Radius ramp scaled by --gem-style-radius (default 1): a 'sharp' style sets it to
|
|
360
490
|
// 0 (square everything), 'pill' rounds further — across every section, no edits.
|
|
361
|
-
...toScaledCssVars("radius", tokens.radius, "--gem-style-radius"),
|
|
491
|
+
...toScaledCssVars("radius", button.groups.radius ?? tokens.radius, "--gem-style-radius"),
|
|
362
492
|
// Border widths get the --gem-style-border knob injected into their leading length,
|
|
363
493
|
// whatever value the plan provides (e.g. "1px solid …" → "calc(1px * var(...)) solid …").
|
|
364
494
|
...toBorderCssVars("border", tokens.border, "--gem-style-border"),
|
|
@@ -367,7 +497,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
367
497
|
// and marquee primitives consume; unrecognised keys fall back to the --gem-motion-* namespace,
|
|
368
498
|
// so duration/easing keep emitting --gem-motion-duration/easing exactly as before (back-compat).
|
|
369
499
|
...toMotionCssVars(tokens.motion),
|
|
370
|
-
...toCssVars("layout", tokens.layout),
|
|
500
|
+
...toCssVars("layout", desktopEntries("layout", tokens.layout)),
|
|
501
|
+
...phone.layout.section ? [`--gem-layout-section-mobile: ${phone.layout.section};`] : [],
|
|
371
502
|
...toCssVars("component", tokens.component),
|
|
372
503
|
...toCssVars("morphology", tokens.morphology),
|
|
373
504
|
// THE GOVERNED TYPE RAMP — --gem-h1..h6 / --gem-body / --gem-text-sm.
|
|
@@ -377,6 +508,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
377
508
|
// variables that resolved to nothing. One modular scale, retuned by overriding --gem-type-base
|
|
378
509
|
// or --gem-type-scale (see gemTypeRampCssVars).
|
|
379
510
|
...gemTypeRampCssVars(tokens.typography),
|
|
511
|
+
// Heading sizes from the ramp, only for a plan that chose a ramp (GEM_HEADING_SIZE_CSS_VARS).
|
|
512
|
+
...typeRampValue(tokens.typography, "base") || typeRampValue(tokens.typography, "scale") ? GEM_HEADING_SIZE_CSS_VARS : [],
|
|
380
513
|
// Focus ring (gemfactory request #3, 28/09/2026): 11 sections wrote `outline: 2px solid
|
|
381
514
|
// var(--gem-color-accent)` by hand. Not a token GROUP (a group would name it --gem-border-focus-ring
|
|
382
515
|
// and a required key would fail every stored plan/preset without it), so it is emitted here like the
|
|
@@ -386,16 +519,25 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
386
519
|
GEM_FOCUS_OFFSET_CSS_VAR
|
|
387
520
|
];
|
|
388
521
|
const formBlock = gemFormCssBlock(tokens.form);
|
|
389
|
-
const
|
|
522
|
+
const buttonCase = gemButtonCaseCss(tokens, selector);
|
|
523
|
+
const mobile = gemMobileTokenCssVars(tokens, phone);
|
|
390
524
|
return [
|
|
391
525
|
`${selector} {`,
|
|
392
526
|
...declarations.map((line) => ` ${line}`),
|
|
393
527
|
"}",
|
|
394
|
-
...
|
|
395
|
-
...formBlock ? [formBlock] : []
|
|
528
|
+
...mobile.length ? [`@media (max-width: ${GEM_TYPE_MOBILE_MAX}) {`, ` ${selector} {`, ...mobile.map((line) => ` ${line}`), " }", "}"] : [],
|
|
529
|
+
...formBlock ? [formBlock] : [],
|
|
530
|
+
...buttonCase ? [buttonCase] : []
|
|
396
531
|
].join("\n");
|
|
397
532
|
}
|
|
398
|
-
|
|
533
|
+
function gemMobileTokenCssVars(tokens, phone = readPhoneTokens(tokens)) {
|
|
534
|
+
const baseMobile = typeRampValue(tokens.typography, "baseMobile");
|
|
535
|
+
return [
|
|
536
|
+
...baseMobile ? [`--gem-type-base: ${baseMobile};`] : [],
|
|
537
|
+
...toScaledCssVars("space", phone.spacing, "--gem-style-space"),
|
|
538
|
+
...toCssVars("layout", phone.layout)
|
|
539
|
+
];
|
|
540
|
+
}
|
|
399
541
|
var rampKey = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
400
542
|
var isRampKey = (k) => GEM_TYPE_RAMP_KEYS.some((r) => rampKey(r) === rampKey(k));
|
|
401
543
|
var LENGTH = /^\d*\.?\d+(px|rem|em)$/;
|
|
@@ -879,10 +1021,10 @@ function createGemFrameworkManifest() {
|
|
|
879
1021
|
package_name: "@gemtheme/gem-framework",
|
|
880
1022
|
framework_version: gemFrameworkVersion,
|
|
881
1023
|
namespace: "gem",
|
|
882
|
-
token_groups: Object.entries(requiredGemTokenKeys).map(([group, keys]) =>
|
|
883
|
-
group
|
|
884
|
-
keys: [...keys]
|
|
885
|
-
})
|
|
1024
|
+
token_groups: Object.entries(requiredGemTokenKeys).map(([group, keys]) => {
|
|
1025
|
+
const optional = [...optionalGemTokenKeys[group] ?? []];
|
|
1026
|
+
return { group, keys: [...keys], ...optional.length ? { optional_keys: optional } : {} };
|
|
1027
|
+
}),
|
|
886
1028
|
primitive_groups: gemFrameworkPrimitiveGroups.map((group) => ({
|
|
887
1029
|
group: group.group,
|
|
888
1030
|
classes: [...group.classes]
|
|
@@ -1186,7 +1328,6 @@ var GEM_AMBIENT_CSS_LINES = [
|
|
|
1186
1328
|
"@media (prefers-reduced-motion: reduce) {",
|
|
1187
1329
|
" [data-gem-ambient] { --gem-ambient-amp: 2px; animation-duration: 12s; }",
|
|
1188
1330
|
" [data-gem-reveal] { transition: none; opacity: 1; transform: none; }",
|
|
1189
|
-
" .gem-marquee__track { animation-duration: calc(var(--gem-marquee-duration, 40s) * 2.5); }",
|
|
1190
1331
|
"}"
|
|
1191
1332
|
];
|
|
1192
1333
|
var GEM_AMBIENT_CSS = GEM_AMBIENT_CSS_LINES.join("\n");
|
|
@@ -1376,9 +1517,13 @@ function createPrimitiveCss() {
|
|
|
1376
1517
|
" justify-content: center;",
|
|
1377
1518
|
" min-height: var(--gem-component-input-height, 44px);",
|
|
1378
1519
|
" border: var(--gem-button-border-width, 1px) solid var(--gem-color-border);",
|
|
1379
|
-
|
|
1520
|
+
// Button tokens (GEM_BUTTON_TOKEN_KEYS, optional): unset, radius and weight fall back to exactly what this rule always
|
|
1521
|
+
// drew. --gem-radius-button is the button's own radius, so a pill button does not turn every card into a pill
|
|
1522
|
+
// (radius-md is shared). Case and tracking are NOT declared here: <button> gets `none` / `normal` from the UA sheet
|
|
1523
|
+
// while <a> inherits, so createGemTokenCss adds them only when a token sets them (gemButtonCaseCss).
|
|
1524
|
+
" border-radius: var(--gem-radius-button, var(--gem-radius-md));",
|
|
1380
1525
|
" padding: var(--gem-component-button-padding-block, 12px) var(--gem-component-button-padding-inline, 18px);",
|
|
1381
|
-
" font-weight: 700;",
|
|
1526
|
+
" font-weight: var(--gem-font-button-weight, 700);",
|
|
1382
1527
|
" text-decoration: none;",
|
|
1383
1528
|
" transition: background var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease), color var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease);",
|
|
1384
1529
|
"}",
|
|
@@ -1641,28 +1786,6 @@ function createPrimitiveCss() {
|
|
|
1641
1786
|
" border-radius: var(--gem-radius-md);",
|
|
1642
1787
|
" background: color-mix(in srgb, var(--gem-color-text) 8%, var(--gem-color-background));",
|
|
1643
1788
|
"}",
|
|
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
1789
|
".gem-carousel {",
|
|
1667
1790
|
" display: grid;",
|
|
1668
1791
|
" gap: var(--gem-space-md);",
|
|
@@ -1798,9 +1921,20 @@ function createPrimitiveCss() {
|
|
|
1798
1921
|
" overflow: hidden;",
|
|
1799
1922
|
" color: var(--gem-color-accent);",
|
|
1800
1923
|
"}",
|
|
1801
|
-
// Marquee primitive
|
|
1802
|
-
//
|
|
1803
|
-
|
|
1924
|
+
// Marquee primitive. CSS-only and deliberately so (no JS to init, works on every render path).
|
|
1925
|
+
// Contract: `.gem-marquee > .gem-marquee__viewport > .gem-marquee__track` TWICE (the second copy
|
|
1926
|
+
// aria-hidden); each track is at least the viewport wide and moves -100% of itself, so the second
|
|
1927
|
+
// copy is what makes the loop seamless. gemcapture's clone kit and GemFactory build exactly this.
|
|
1928
|
+
// ONE definition (0.9.11): until then a second, earlier one (gemtheme #124: one track with its
|
|
1929
|
+
// children doubled, -50%) sat above this and lost every property both set, so its contract never
|
|
1930
|
+
// worked. What it set that still took effect (the border, will-change, pause on hover of the whole
|
|
1931
|
+
// marquee, the data-attribute reverse) is kept here, so a marquee built to either contract computes
|
|
1932
|
+
// the same style as before (Chromium, 08/10/2026). The one exception would be a track that also
|
|
1933
|
+
// carries data-gem-ambient, which nothing renders.
|
|
1934
|
+
".gem-marquee {",
|
|
1935
|
+
" overflow: hidden;",
|
|
1936
|
+
" border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1937
|
+
"}",
|
|
1804
1938
|
".gem-marquee__viewport { display: flex; overflow: hidden; }",
|
|
1805
1939
|
".gem-marquee__track {",
|
|
1806
1940
|
" flex: 0 0 auto;",
|
|
@@ -1811,9 +1945,12 @@ function createPrimitiveCss() {
|
|
|
1811
1945
|
" gap: var(--gem-space-lg);",
|
|
1812
1946
|
" white-space: nowrap;",
|
|
1813
1947
|
" animation: gem-marquee var(--gem-marquee-duration, 24s) linear infinite;",
|
|
1948
|
+
" will-change: transform;",
|
|
1814
1949
|
"}",
|
|
1815
|
-
|
|
1816
|
-
".gem-marquee
|
|
1950
|
+
// Pause-on-hover lets a shopper read a badge/logo they want to inspect.
|
|
1951
|
+
".gem-marquee:hover .gem-marquee__track, .gem-marquee__viewport:hover .gem-marquee__track { animation-play-state: paused; }",
|
|
1952
|
+
// Opt-in reverse direction (e.g. a second row scrolling the other way).
|
|
1953
|
+
".gem-marquee--reverse .gem-marquee__track, .gem-marquee[data-gem-marquee-reverse] .gem-marquee__track { animation-direction: reverse; }",
|
|
1817
1954
|
"@keyframes gem-marquee { from { transform: translateX(0); } to { transform: translateX(-100%); } }",
|
|
1818
1955
|
"@media (prefers-reduced-motion: reduce) {",
|
|
1819
1956
|
" .gem-marquee__track { animation: none; }",
|
|
@@ -2831,10 +2968,10 @@ function createGemSectionRuntimeContractCss(selector = "[data-gem-section-librar
|
|
|
2831
2968
|
"}"
|
|
2832
2969
|
].join("\n");
|
|
2833
2970
|
}
|
|
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; }";
|
|
2971
|
+
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; }";
|
|
2972
|
+
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
2973
|
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)); }";
|
|
2974
|
+
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
2975
|
function createSectionCss() {
|
|
2839
2976
|
return [
|
|
2840
2977
|
".gem-section {",
|
|
@@ -3026,8 +3163,8 @@ function createSectionCss() {
|
|
|
3026
3163
|
".gem-section__heading {",
|
|
3027
3164
|
" margin: 0;",
|
|
3028
3165
|
" 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;",
|
|
3166
|
+
" 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))));",
|
|
3167
|
+
" line-height: var(--gem-font-heading-line-height, 1.04);",
|
|
3031
3168
|
"}",
|
|
3032
3169
|
".gem-section__body {",
|
|
3033
3170
|
" margin: 0;",
|
|
@@ -3144,9 +3281,10 @@ function createSectionCss() {
|
|
|
3144
3281
|
" }",
|
|
3145
3282
|
"}",
|
|
3146
3283
|
"@media (max-width: 760px) {",
|
|
3147
|
-
|
|
3284
|
+
// A declared layout.sectionMobile is final (tokens.ts GEM_PHONE_TOKEN_TARGETS); otherwise the desktop value × 0.72.
|
|
3285
|
+
" .gem-section { padding-block: var(--gem-layout-section-mobile, calc(var(--gem-layout-section, 64px) * 0.72)); }",
|
|
3148
3286
|
" .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); }",
|
|
3287
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, clamp(1.75rem, 9vw, 2.75rem)); }",
|
|
3150
3288
|
" .gem-section__grid { grid-template-columns: minmax(0, 1fr); }",
|
|
3151
3289
|
" .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
3290
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-main__inner { grid-template-columns: minmax(0, 1fr); }",
|
|
@@ -3166,7 +3304,7 @@ function createSectionCss() {
|
|
|
3166
3304
|
function createDawnParityCss() {
|
|
3167
3305
|
return [
|
|
3168
3306
|
"/* 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; }",
|
|
3307
|
+
":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
3308
|
".utility-bar, .announcement-bar, .utility-bar__grid { min-height: 38px; }",
|
|
3171
3309
|
".announcement-bar__message { margin: 0; min-height: 38px; padding-block: 10px; font-size: 0.75rem; font-weight: 400; letter-spacing: 0.04em; }",
|
|
3172
3310
|
// The pair of rules here used to blank the real message (font-size: 0) and inject a FIXED
|
|
@@ -3242,7 +3380,12 @@ function createDawnParityCss() {
|
|
|
3242
3380
|
".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
3381
|
".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
3382
|
".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-
|
|
3383
|
+
".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; }",
|
|
3384
|
+
// Font size has ONE owner per selector (one-owner-per-selector.test.ts): section headings take H2 of the plan's ramp,
|
|
3385
|
+
// the hero's own title H1 (tokens.ts GEM_HEADING_SIZE_CSS_VARS). Same position and specificity as the shared rule above,
|
|
3386
|
+
// so with no ramp both resolve to the clamp that rule used to set.
|
|
3387
|
+
".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)))); }",
|
|
3388
|
+
".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
3389
|
".gem-section__eyebrow { display: none; }",
|
|
3247
3390
|
".gem-hero-banner--compact-collection { min-height: auto; display: block; text-align: left; padding-block: 25px; }",
|
|
3248
3391
|
".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 +3400,13 @@ function createDawnParityCss() {
|
|
|
3257
3400
|
".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
3401
|
".gem-rich-text--dawn-intro .gem-section__body p { margin: 0; }",
|
|
3259
3402
|
".gem-rich-text--dawn-intro .gem-rich-text__blocks { display: block; width: 100%; }",
|
|
3260
|
-
|
|
3403
|
+
// Dawn's square, regular-weight button stays the default; the button tokens (radius.button, typography.buttonWeight)
|
|
3404
|
+
// override it, or this rule (same specificity, later) would pin every button square at 400 whatever the plan says.
|
|
3405
|
+
// The weight sits in its own rule that skips link buttons, which keep today's 400 here: the rule above it is what
|
|
3406
|
+
// overrides `.gem-button--link { font-weight: inherit }` in this profile, and a weight token must not reach a link.
|
|
3407
|
+
// `:where()` keeps both at one class of specificity, so a section's own CTA rule still wins.
|
|
3408
|
+
".gem-button { min-width: 12rem; border-radius: var(--gem-radius-button, 0); font-weight: 400; box-shadow: none; }",
|
|
3409
|
+
".gem-button:where(:not(.gem-button--link)) { font-weight: var(--gem-font-button-weight, 400); }",
|
|
3261
3410
|
".gem-product-card-wrapper { display: block; min-width: 0; }",
|
|
3262
3411
|
".gem-card, .gem-product-card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }",
|
|
3263
3412
|
".gem-product-card__media, .gem-media { border: 0; border-radius: 0; box-shadow: none; background: rgb(244 244 244); }",
|
|
@@ -3453,7 +3602,9 @@ function createDawnParityMobileCss() {
|
|
|
3453
3602
|
" .gem-product-card__content { padding-top: 8px; gap: 4px; }",
|
|
3454
3603
|
" .gem-product-card__title { font-size: 15px; line-height: 24px; }",
|
|
3455
3604
|
" .gem-product-card__price, .gem-product-card__price .gem-price { font-size: 16px; line-height: 24px; }",
|
|
3456
|
-
|
|
3605
|
+
// Fallback = the profile's own phone heading, declared once as --gem-heading-phone-cap on the :root above.
|
|
3606
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3607
|
+
" .gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3457
3608
|
" .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
3609
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] > .gem-section__inner { width: calc(100% - 30px); }",
|
|
3459
3610
|
" .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 +5878,10 @@ function gemFrameworkPublicCssVariables() {
|
|
|
5727
5878
|
createPrimitiveCss(),
|
|
5728
5879
|
String(createSectionCss()),
|
|
5729
5880
|
String(createShellCss()),
|
|
5730
|
-
String(createGemSectionRuntimeContractCss())
|
|
5881
|
+
String(createGemSectionRuntimeContractCss()),
|
|
5882
|
+
// The button case rule only exists when a token sets it; a section may still read its variables (a CTA rule
|
|
5883
|
+
// beats the zero-specificity rule, so opting in means `text-transform: var(--gem-font-button-transform, none)`).
|
|
5884
|
+
gemButtonCaseCss({ typography: { buttonTransform: "none", buttonLetterSpacing: "normal" } })
|
|
5731
5885
|
].join("\n").replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
5732
5886
|
frameworkPublicVariables = gemPublicCssVariablesFrom(css);
|
|
5733
5887
|
}
|
|
@@ -6898,6 +7052,8 @@ function stringSetting(value) {
|
|
|
6898
7052
|
export {
|
|
6899
7053
|
COUNTDOWN_END_RUNTIME_LINES,
|
|
6900
7054
|
GEM_AMBIENT_CSS,
|
|
7055
|
+
GEM_BUTTON_TOKEN_KEYS,
|
|
7056
|
+
GEM_BUTTON_TOKEN_SHAPES,
|
|
6901
7057
|
GEM_COUNTDOWN_ENDED_CSS,
|
|
6902
7058
|
GEM_FIELD_JOINS,
|
|
6903
7059
|
GEM_FIELD_SHAPES,
|
|
@@ -6908,6 +7064,8 @@ export {
|
|
|
6908
7064
|
GEM_FORM_TOKEN_KEYS,
|
|
6909
7065
|
GEM_FORM_VAR_SCOPE,
|
|
6910
7066
|
GEM_GRID_COLUMNS_ROW_KNOB_CSS,
|
|
7067
|
+
GEM_HEADING_SIZE_CSS_VARS,
|
|
7068
|
+
GEM_LINE_HEIGHT_TOKEN_KEYS,
|
|
6911
7069
|
GEM_PART_CLASS,
|
|
6912
7070
|
GEM_POPUP_SHELL_CSS,
|
|
6913
7071
|
GEM_REVEAL_CSS,
|
|
@@ -6939,6 +7097,7 @@ export {
|
|
|
6939
7097
|
createThemeProfileCss,
|
|
6940
7098
|
defaultGemTokens,
|
|
6941
7099
|
defaultSelection,
|
|
7100
|
+
gemButtonCaseCss,
|
|
6942
7101
|
gemFormAttributes,
|
|
6943
7102
|
gemFormCssBlock,
|
|
6944
7103
|
gemFormCssVars,
|
|
@@ -6947,7 +7106,9 @@ export {
|
|
|
6947
7106
|
gemFrameworkRuntimePlugins,
|
|
6948
7107
|
gemFrameworkValidationRules,
|
|
6949
7108
|
gemFrameworkVersion,
|
|
7109
|
+
gemMobileTokenCssVars,
|
|
6950
7110
|
gemPartClass,
|
|
7111
|
+
gemPhoneTokenTarget,
|
|
6951
7112
|
gemPlanShapeKnobs,
|
|
6952
7113
|
gemRuntimePluginModuleKeys,
|
|
6953
7114
|
gemRuntimePluginSources,
|
|
@@ -6958,6 +7119,7 @@ export {
|
|
|
6958
7119
|
motionCssVarAliases,
|
|
6959
7120
|
normalizeGemTokens,
|
|
6960
7121
|
offerEngineRuntimeScript,
|
|
7122
|
+
optionalGemTokenKeys,
|
|
6961
7123
|
planToRuntimeBootstrapConfig,
|
|
6962
7124
|
renderGemFrameworkManifest,
|
|
6963
7125
|
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%; }
|