@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/section-gate.js
CHANGED
|
@@ -104,6 +104,73 @@ var GEM_ROW_CSS_VARIABLES = [
|
|
|
104
104
|
"--gem-row-gap",
|
|
105
105
|
"--gem-row-gap-y"
|
|
106
106
|
];
|
|
107
|
+
var GEM_BUTTON_TOKEN_KEYS = {
|
|
108
|
+
radius: ["button"],
|
|
109
|
+
typography: ["buttonWeight", "buttonTransform", "buttonLetterSpacing"]
|
|
110
|
+
};
|
|
111
|
+
var GEM_BUTTON_TOKEN_SHAPES = {
|
|
112
|
+
"radius.button": /^(0|\d*\.?\d+(px|rem|em|%)|var\(--gem-radius-[a-z0-9-]+\))$/i,
|
|
113
|
+
"typography.buttonWeight": /^(normal|bold|\d{1,4})$/i,
|
|
114
|
+
"typography.buttonTransform": /^(none|uppercase|lowercase|capitalize)$/i,
|
|
115
|
+
"typography.buttonLetterSpacing": /^(normal|0|-?\d*\.?\d+(em|px|rem))$/i
|
|
116
|
+
};
|
|
117
|
+
var buttonKeyNorm = (k) => k.toLowerCase().replace(/[_-]/g, "");
|
|
118
|
+
function buttonTokenValue(group, key, value) {
|
|
119
|
+
const shape = GEM_BUTTON_TOKEN_SHAPES[`${group}.${key}`];
|
|
120
|
+
if (!shape) return null;
|
|
121
|
+
const v = typeof value === "number" ? String(value) : typeof value === "string" ? stripImportant(value).trim() : "";
|
|
122
|
+
if (!shape.test(v)) return null;
|
|
123
|
+
if (key === "buttonWeight" && /^\d+$/.test(v) && (Number(v) < 1 || Number(v) > 1e3)) return null;
|
|
124
|
+
if (key === "button" && v === "0") return "0px";
|
|
125
|
+
return /^[a-z]+$/i.test(v) ? v.toLowerCase() : v.replace(/(px|rem|em)$/i, (u) => u.toLowerCase());
|
|
126
|
+
}
|
|
127
|
+
function readButtonTokens(tokens) {
|
|
128
|
+
const out = { groups: {}, values: {}, invalid: [] };
|
|
129
|
+
for (const [group, keys] of Object.entries(GEM_BUTTON_TOKEN_KEYS)) {
|
|
130
|
+
const values = tokens?.[group];
|
|
131
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) continue;
|
|
132
|
+
const byNorm = new Map(keys.map((k) => [buttonKeyNorm(k), k]));
|
|
133
|
+
let copy = null;
|
|
134
|
+
for (const [rawKey, raw] of Object.entries(values)) {
|
|
135
|
+
const key = byNorm.get(buttonKeyNorm(rawKey));
|
|
136
|
+
if (!key) continue;
|
|
137
|
+
copy ??= { ...values };
|
|
138
|
+
delete copy[rawKey];
|
|
139
|
+
const v = buttonTokenValue(group, key, raw);
|
|
140
|
+
if (v === null) out.invalid.push({ path: `${group}.${rawKey}`, value: raw });
|
|
141
|
+
else {
|
|
142
|
+
copy[key] = v;
|
|
143
|
+
out.values[key] = v;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (copy) out.groups[group] = copy;
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
function splitSelectorList(selector) {
|
|
151
|
+
const parts = [];
|
|
152
|
+
let depth = 0, cur = "";
|
|
153
|
+
for (const ch of selector) {
|
|
154
|
+
if (ch === "(" || ch === "[") depth++;
|
|
155
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
156
|
+
if (ch === "," && depth === 0) {
|
|
157
|
+
parts.push(cur.trim());
|
|
158
|
+
cur = "";
|
|
159
|
+
} else cur += ch;
|
|
160
|
+
}
|
|
161
|
+
if (cur.trim()) parts.push(cur.trim());
|
|
162
|
+
return parts;
|
|
163
|
+
}
|
|
164
|
+
function gemButtonCaseCss(tokens, selector = ":root") {
|
|
165
|
+
const { values } = readButtonTokens(tokens);
|
|
166
|
+
const decls = [
|
|
167
|
+
values.buttonTransform && "text-transform: var(--gem-font-button-transform);",
|
|
168
|
+
values.buttonLetterSpacing && "letter-spacing: var(--gem-font-button-letter-spacing);"
|
|
169
|
+
].filter(Boolean);
|
|
170
|
+
if (!decls.length) return "";
|
|
171
|
+
const scope = splitSelectorList(selector).map((sel) => `${sel} .gem-button:not(.gem-button--link)`).join(", ");
|
|
172
|
+
return `:where(${scope}) { ${decls.join(" ")} }`;
|
|
173
|
+
}
|
|
107
174
|
var requiredGemTokenKeys = {
|
|
108
175
|
color: ["background", "surface", "text", "muted", "accent", "accentText", "border", "success", "warning", "danger"],
|
|
109
176
|
typography: ["body", "heading", "mono"],
|
|
@@ -116,6 +183,52 @@ var requiredGemTokenKeys = {
|
|
|
116
183
|
component: ["headerHeight", "cardPadding", "buttonPaddingInline", "buttonPaddingBlock", "inputHeight"],
|
|
117
184
|
morphology: ["campaignDensity", "mediaRhythm", "proofIntensity", "commerceEmphasis", "policyTone"]
|
|
118
185
|
};
|
|
186
|
+
var GEM_PHONE_TOKEN_TARGETS = {
|
|
187
|
+
spacing: [...requiredGemTokenKeys.spacing, ...Object.keys(GEM_DERIVED_SPACE_STEPS)],
|
|
188
|
+
layout: ["section"]
|
|
189
|
+
};
|
|
190
|
+
var tokenKeyNorm = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
191
|
+
var PHONE_TARGET_BY_KEY = Object.fromEntries(
|
|
192
|
+
Object.entries(GEM_PHONE_TOKEN_TARGETS).map(([group, targets]) => [group, new Map(targets.map((t) => [`${tokenKeyNorm(t)}mobile`, t]))])
|
|
193
|
+
);
|
|
194
|
+
function gemPhoneTokenTarget(group, key) {
|
|
195
|
+
return PHONE_TARGET_BY_KEY[group]?.get(tokenKeyNorm(key)) ?? null;
|
|
196
|
+
}
|
|
197
|
+
function readPhoneTokens(tokens) {
|
|
198
|
+
const out = { spacing: {}, layout: {}, invalid: [] };
|
|
199
|
+
for (const group of ["spacing", "layout"]) {
|
|
200
|
+
const ownGroup = group === "spacing" ? /var\(\s*--gem-space-/i : /var\(\s*--gem-layout-/i;
|
|
201
|
+
for (const [key, raw] of Object.entries(tokens?.[group] ?? {})) {
|
|
202
|
+
const target = gemPhoneTokenTarget(group, key);
|
|
203
|
+
if (target == null) continue;
|
|
204
|
+
const v = stripImportant(String(raw ?? "")).trim();
|
|
205
|
+
const kind = gemSpaceMdKind(v);
|
|
206
|
+
if (kind === "absent") continue;
|
|
207
|
+
if (kind === "zero") out[group][target] = "0px";
|
|
208
|
+
else if (kind === "length" && !ownGroup.test(v)) out[group][target] = v;
|
|
209
|
+
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)" });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
var GEM_LINE_HEIGHT_TOKEN_KEYS = ["headingLineHeight", "bodyLineHeight"];
|
|
215
|
+
var LINE_HEIGHT_SHAPE = /^(normal|\d*\.?\d+(px|rem|em|%)?|(var|calc|clamp|min|max)\(.*\))$/i;
|
|
216
|
+
var isLineHeightKey = (k) => GEM_LINE_HEIGHT_TOKEN_KEYS.some((l) => tokenKeyNorm(l) === tokenKeyNorm(k));
|
|
217
|
+
var badLineHeight = (k, v) => isLineHeightKey(k) && !LINE_HEIGHT_SHAPE.test(stripImportant(String(v ?? "")).trim());
|
|
218
|
+
var desktopEntries = (group, values) => values ? Object.fromEntries(Object.entries(values).filter(([key]) => gemPhoneTokenTarget(group, key) == null)) : values;
|
|
219
|
+
var GEM_TYPE_RAMP_KEYS = ["base", "scale", "baseMobile"];
|
|
220
|
+
var optionalGemTokenKeys = {
|
|
221
|
+
typography: [...GEM_TYPE_RAMP_KEYS, ...GEM_LINE_HEIGHT_TOKEN_KEYS, ...GEM_BUTTON_TOKEN_KEYS.typography],
|
|
222
|
+
spacing: [...Object.keys(GEM_DERIVED_SPACE_STEPS), ...GEM_PHONE_TOKEN_TARGETS.spacing.map((k) => `${k}Mobile`)],
|
|
223
|
+
layout: GEM_PHONE_TOKEN_TARGETS.layout.map((k) => `${k}Mobile`),
|
|
224
|
+
radius: [...GEM_BUTTON_TOKEN_KEYS.radius]
|
|
225
|
+
};
|
|
226
|
+
var GEM_HEADING_SIZE_CSS_VARS = [
|
|
227
|
+
"--gem-section-heading-size: calc(var(--gem-h2) * var(--gem-typography-heading-scale, 1));",
|
|
228
|
+
"--gem-hero-heading-size: calc(var(--gem-h1) * var(--gem-typography-heading-scale, 1));",
|
|
229
|
+
"--gem-section-heading-size-mobile: min(var(--gem-section-heading-size), var(--gem-heading-phone-cap, clamp(1.75rem, 9vw, 2.75rem)));",
|
|
230
|
+
"--gem-hero-heading-size-mobile: min(var(--gem-hero-heading-size), var(--gem-heading-phone-cap, clamp(1.75rem, 9vw, 2.75rem)));"
|
|
231
|
+
];
|
|
119
232
|
var defaultGemTokens = {
|
|
120
233
|
color: {
|
|
121
234
|
background: "#f6f6f3",
|
|
@@ -129,7 +242,10 @@ var defaultGemTokens = {
|
|
|
129
242
|
accentText: "#ffffff",
|
|
130
243
|
border: "#d2d5d8",
|
|
131
244
|
success: "#008060",
|
|
132
|
-
|
|
245
|
+
// 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
|
|
246
|
+
// GemFactory sections set `color: var(--gem-color-warning)` on words; white text on a warning badge was 3.64:1 too.
|
|
247
|
+
// #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.
|
|
248
|
+
warning: "#9a661a",
|
|
133
249
|
danger: "#d72c0d"
|
|
134
250
|
},
|
|
135
251
|
typography: {
|
|
@@ -194,17 +310,21 @@ var defaultGemTokens = {
|
|
|
194
310
|
}
|
|
195
311
|
};
|
|
196
312
|
function createGemTokenCss(tokens, selector = ":root") {
|
|
313
|
+
const button = readButtonTokens(tokens);
|
|
314
|
+
const phone = readPhoneTokens(tokens);
|
|
315
|
+
const spacing = desktopEntries("spacing", tokens.spacing);
|
|
197
316
|
const declarations = [
|
|
198
317
|
...toCssVars("color", tokens.color),
|
|
199
|
-
...toCssVars("font", Object.fromEntries(Object.entries(tokens.typography ?? {}).filter(([k]) => !isRampKey(k)))),
|
|
318
|
+
...toCssVars("font", Object.fromEntries(Object.entries(button.groups.typography ?? tokens.typography ?? {}).filter(([k, v]) => !isRampKey(k) && !badLineHeight(k, v)))),
|
|
200
319
|
// Space ramp is scaled by the global density knob --gem-style-space (default 1),
|
|
201
320
|
// so a style pack can make the WHOLE storefront airier or denser without touching
|
|
202
321
|
// any section — every section already consumes --gem-space-*. Knob unset → identical.
|
|
203
|
-
|
|
204
|
-
...
|
|
322
|
+
// Phone keys (`3xlMobile`) are not desktop variables: they go to the phone block below (GEM_PHONE_TOKEN_TARGETS).
|
|
323
|
+
...toScaledCssVars("space", spacing, "--gem-style-space"),
|
|
324
|
+
...gemDerivedSpaceCssVars(spacing),
|
|
205
325
|
// Radius ramp scaled by --gem-style-radius (default 1): a 'sharp' style sets it to
|
|
206
326
|
// 0 (square everything), 'pill' rounds further — across every section, no edits.
|
|
207
|
-
...toScaledCssVars("radius", tokens.radius, "--gem-style-radius"),
|
|
327
|
+
...toScaledCssVars("radius", button.groups.radius ?? tokens.radius, "--gem-style-radius"),
|
|
208
328
|
// Border widths get the --gem-style-border knob injected into their leading length,
|
|
209
329
|
// whatever value the plan provides (e.g. "1px solid …" → "calc(1px * var(...)) solid …").
|
|
210
330
|
...toBorderCssVars("border", tokens.border, "--gem-style-border"),
|
|
@@ -213,7 +333,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
213
333
|
// and marquee primitives consume; unrecognised keys fall back to the --gem-motion-* namespace,
|
|
214
334
|
// so duration/easing keep emitting --gem-motion-duration/easing exactly as before (back-compat).
|
|
215
335
|
...toMotionCssVars(tokens.motion),
|
|
216
|
-
...toCssVars("layout", tokens.layout),
|
|
336
|
+
...toCssVars("layout", desktopEntries("layout", tokens.layout)),
|
|
337
|
+
...phone.layout.section ? [`--gem-layout-section-mobile: ${phone.layout.section};`] : [],
|
|
217
338
|
...toCssVars("component", tokens.component),
|
|
218
339
|
...toCssVars("morphology", tokens.morphology),
|
|
219
340
|
// THE GOVERNED TYPE RAMP — --gem-h1..h6 / --gem-body / --gem-text-sm.
|
|
@@ -223,6 +344,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
223
344
|
// variables that resolved to nothing. One modular scale, retuned by overriding --gem-type-base
|
|
224
345
|
// or --gem-type-scale (see gemTypeRampCssVars).
|
|
225
346
|
...gemTypeRampCssVars(tokens.typography),
|
|
347
|
+
// Heading sizes from the ramp, only for a plan that chose a ramp (GEM_HEADING_SIZE_CSS_VARS).
|
|
348
|
+
...typeRampValue(tokens.typography, "base") || typeRampValue(tokens.typography, "scale") ? GEM_HEADING_SIZE_CSS_VARS : [],
|
|
226
349
|
// Focus ring (gemfactory request #3, 28/09/2026): 11 sections wrote `outline: 2px solid
|
|
227
350
|
// var(--gem-color-accent)` by hand. Not a token GROUP (a group would name it --gem-border-focus-ring
|
|
228
351
|
// and a required key would fail every stored plan/preset without it), so it is emitted here like the
|
|
@@ -232,16 +355,25 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
232
355
|
GEM_FOCUS_OFFSET_CSS_VAR
|
|
233
356
|
];
|
|
234
357
|
const formBlock = gemFormCssBlock(tokens.form);
|
|
235
|
-
const
|
|
358
|
+
const buttonCase = gemButtonCaseCss(tokens, selector);
|
|
359
|
+
const mobile = gemMobileTokenCssVars(tokens, phone);
|
|
236
360
|
return [
|
|
237
361
|
`${selector} {`,
|
|
238
362
|
...declarations.map((line) => ` ${line}`),
|
|
239
363
|
"}",
|
|
240
|
-
...
|
|
241
|
-
...formBlock ? [formBlock] : []
|
|
364
|
+
...mobile.length ? [`@media (max-width: ${GEM_TYPE_MOBILE_MAX}) {`, ` ${selector} {`, ...mobile.map((line) => ` ${line}`), " }", "}"] : [],
|
|
365
|
+
...formBlock ? [formBlock] : [],
|
|
366
|
+
...buttonCase ? [buttonCase] : []
|
|
242
367
|
].join("\n");
|
|
243
368
|
}
|
|
244
|
-
|
|
369
|
+
function gemMobileTokenCssVars(tokens, phone = readPhoneTokens(tokens)) {
|
|
370
|
+
const baseMobile = typeRampValue(tokens.typography, "baseMobile");
|
|
371
|
+
return [
|
|
372
|
+
...baseMobile ? [`--gem-type-base: ${baseMobile};`] : [],
|
|
373
|
+
...toScaledCssVars("space", phone.spacing, "--gem-style-space"),
|
|
374
|
+
...toCssVars("layout", phone.layout)
|
|
375
|
+
];
|
|
376
|
+
}
|
|
245
377
|
var rampKey = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
246
378
|
var isRampKey = (k) => GEM_TYPE_RAMP_KEYS.some((r) => rampKey(r) === rampKey(k));
|
|
247
379
|
var LENGTH = /^\d*\.?\d+(px|rem|em)$/;
|
|
@@ -890,7 +1022,6 @@ var GEM_AMBIENT_CSS_LINES = [
|
|
|
890
1022
|
"@media (prefers-reduced-motion: reduce) {",
|
|
891
1023
|
" [data-gem-ambient] { --gem-ambient-amp: 2px; animation-duration: 12s; }",
|
|
892
1024
|
" [data-gem-reveal] { transition: none; opacity: 1; transform: none; }",
|
|
893
|
-
" .gem-marquee__track { animation-duration: calc(var(--gem-marquee-duration, 40s) * 2.5); }",
|
|
894
1025
|
"}"
|
|
895
1026
|
];
|
|
896
1027
|
var GEM_AMBIENT_CSS = GEM_AMBIENT_CSS_LINES.join("\n");
|
|
@@ -1073,9 +1204,13 @@ function createPrimitiveCss() {
|
|
|
1073
1204
|
" justify-content: center;",
|
|
1074
1205
|
" min-height: var(--gem-component-input-height, 44px);",
|
|
1075
1206
|
" border: var(--gem-button-border-width, 1px) solid var(--gem-color-border);",
|
|
1076
|
-
|
|
1207
|
+
// Button tokens (GEM_BUTTON_TOKEN_KEYS, optional): unset, radius and weight fall back to exactly what this rule always
|
|
1208
|
+
// drew. --gem-radius-button is the button's own radius, so a pill button does not turn every card into a pill
|
|
1209
|
+
// (radius-md is shared). Case and tracking are NOT declared here: <button> gets `none` / `normal` from the UA sheet
|
|
1210
|
+
// while <a> inherits, so createGemTokenCss adds them only when a token sets them (gemButtonCaseCss).
|
|
1211
|
+
" border-radius: var(--gem-radius-button, var(--gem-radius-md));",
|
|
1077
1212
|
" padding: var(--gem-component-button-padding-block, 12px) var(--gem-component-button-padding-inline, 18px);",
|
|
1078
|
-
" font-weight: 700;",
|
|
1213
|
+
" font-weight: var(--gem-font-button-weight, 700);",
|
|
1079
1214
|
" text-decoration: none;",
|
|
1080
1215
|
" transition: background var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease), color var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease);",
|
|
1081
1216
|
"}",
|
|
@@ -1338,28 +1473,6 @@ function createPrimitiveCss() {
|
|
|
1338
1473
|
" border-radius: var(--gem-radius-md);",
|
|
1339
1474
|
" background: color-mix(in srgb, var(--gem-color-text) 8%, var(--gem-color-background));",
|
|
1340
1475
|
"}",
|
|
1341
|
-
".gem-marquee {",
|
|
1342
|
-
" overflow: hidden;",
|
|
1343
|
-
" border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1344
|
-
"}",
|
|
1345
|
-
// Consumer contract: render the track's children TWICE (like the site's `.mq-row`) so the
|
|
1346
|
-
// -50% translate lands on an identical second copy and the loop is seamless with no visible jump.
|
|
1347
|
-
// The animation reuses the gem-marquee-scroll keyframe defined with the ambient primitive.
|
|
1348
|
-
".gem-marquee__track {",
|
|
1349
|
-
" display: flex;",
|
|
1350
|
-
" gap: var(--gem-space-lg);",
|
|
1351
|
-
" min-width: max-content;",
|
|
1352
|
-
" animation: gem-marquee-scroll var(--gem-marquee-duration, 40s) linear infinite;",
|
|
1353
|
-
" will-change: transform;",
|
|
1354
|
-
"}",
|
|
1355
|
-
// Pause-on-hover lets a shopper read a badge/logo they want to inspect.
|
|
1356
|
-
".gem-marquee:hover .gem-marquee__track {",
|
|
1357
|
-
" animation-play-state: paused;",
|
|
1358
|
-
"}",
|
|
1359
|
-
// Opt-in reverse direction (e.g. a second row scrolling the other way).
|
|
1360
|
-
".gem-marquee[data-gem-marquee-reverse] .gem-marquee__track {",
|
|
1361
|
-
" animation-direction: reverse;",
|
|
1362
|
-
"}",
|
|
1363
1476
|
".gem-carousel {",
|
|
1364
1477
|
" display: grid;",
|
|
1365
1478
|
" gap: var(--gem-space-md);",
|
|
@@ -1495,9 +1608,20 @@ function createPrimitiveCss() {
|
|
|
1495
1608
|
" overflow: hidden;",
|
|
1496
1609
|
" color: var(--gem-color-accent);",
|
|
1497
1610
|
"}",
|
|
1498
|
-
// Marquee primitive
|
|
1499
|
-
//
|
|
1500
|
-
|
|
1611
|
+
// Marquee primitive. CSS-only and deliberately so (no JS to init, works on every render path).
|
|
1612
|
+
// Contract: `.gem-marquee > .gem-marquee__viewport > .gem-marquee__track` TWICE (the second copy
|
|
1613
|
+
// aria-hidden); each track is at least the viewport wide and moves -100% of itself, so the second
|
|
1614
|
+
// copy is what makes the loop seamless. gemcapture's clone kit and GemFactory build exactly this.
|
|
1615
|
+
// ONE definition (0.9.11): until then a second, earlier one (gemtheme #124: one track with its
|
|
1616
|
+
// children doubled, -50%) sat above this and lost every property both set, so its contract never
|
|
1617
|
+
// worked. What it set that still took effect (the border, will-change, pause on hover of the whole
|
|
1618
|
+
// marquee, the data-attribute reverse) is kept here, so a marquee built to either contract computes
|
|
1619
|
+
// the same style as before (Chromium, 08/10/2026). The one exception would be a track that also
|
|
1620
|
+
// carries data-gem-ambient, which nothing renders.
|
|
1621
|
+
".gem-marquee {",
|
|
1622
|
+
" overflow: hidden;",
|
|
1623
|
+
" border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1624
|
+
"}",
|
|
1501
1625
|
".gem-marquee__viewport { display: flex; overflow: hidden; }",
|
|
1502
1626
|
".gem-marquee__track {",
|
|
1503
1627
|
" flex: 0 0 auto;",
|
|
@@ -1508,9 +1632,12 @@ function createPrimitiveCss() {
|
|
|
1508
1632
|
" gap: var(--gem-space-lg);",
|
|
1509
1633
|
" white-space: nowrap;",
|
|
1510
1634
|
" animation: gem-marquee var(--gem-marquee-duration, 24s) linear infinite;",
|
|
1635
|
+
" will-change: transform;",
|
|
1511
1636
|
"}",
|
|
1512
|
-
|
|
1513
|
-
".gem-marquee
|
|
1637
|
+
// Pause-on-hover lets a shopper read a badge/logo they want to inspect.
|
|
1638
|
+
".gem-marquee:hover .gem-marquee__track, .gem-marquee__viewport:hover .gem-marquee__track { animation-play-state: paused; }",
|
|
1639
|
+
// Opt-in reverse direction (e.g. a second row scrolling the other way).
|
|
1640
|
+
".gem-marquee--reverse .gem-marquee__track, .gem-marquee[data-gem-marquee-reverse] .gem-marquee__track { animation-direction: reverse; }",
|
|
1514
1641
|
"@keyframes gem-marquee { from { transform: translateX(0); } to { transform: translateX(-100%); } }",
|
|
1515
1642
|
"@media (prefers-reduced-motion: reduce) {",
|
|
1516
1643
|
" .gem-marquee__track { animation: none; }",
|
|
@@ -2528,10 +2655,10 @@ function createGemSectionRuntimeContractCss(selector = "[data-gem-section-librar
|
|
|
2528
2655
|
"}"
|
|
2529
2656
|
].join("\n");
|
|
2530
2657
|
}
|
|
2531
|
-
var OPT_IN_EYEBROW = ":where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; letter-spacing: 0.12em; }";
|
|
2532
|
-
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; }";
|
|
2658
|
+
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; }";
|
|
2659
|
+
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; }";
|
|
2533
2660
|
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); }";
|
|
2534
|
-
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)); }";
|
|
2661
|
+
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))); }";
|
|
2535
2662
|
function createSectionCss() {
|
|
2536
2663
|
return [
|
|
2537
2664
|
".gem-section {",
|
|
@@ -2723,8 +2850,8 @@ function createSectionCss() {
|
|
|
2723
2850
|
".gem-section__heading {",
|
|
2724
2851
|
" margin: 0;",
|
|
2725
2852
|
" font-family: var(--gem-font-heading);",
|
|
2726
|
-
" 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)));",
|
|
2727
|
-
" line-height: 1.04;",
|
|
2853
|
+
" 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))));",
|
|
2854
|
+
" line-height: var(--gem-font-heading-line-height, 1.04);",
|
|
2728
2855
|
"}",
|
|
2729
2856
|
".gem-section__body {",
|
|
2730
2857
|
" margin: 0;",
|
|
@@ -2841,9 +2968,10 @@ function createSectionCss() {
|
|
|
2841
2968
|
" }",
|
|
2842
2969
|
"}",
|
|
2843
2970
|
"@media (max-width: 760px) {",
|
|
2844
|
-
|
|
2971
|
+
// A declared layout.sectionMobile is final (tokens.ts GEM_PHONE_TOKEN_TARGETS); otherwise the desktop value × 0.72.
|
|
2972
|
+
" .gem-section { padding-block: var(--gem-layout-section-mobile, calc(var(--gem-layout-section, 64px) * 0.72)); }",
|
|
2845
2973
|
" .gem-section__inner, .gem-container { width: min(100% - 32px, var(--gem-layout-page, 1180px)); }",
|
|
2846
|
-
" .gem-section__heading { font-size: clamp(1.75rem, 9vw, 2.75rem); }",
|
|
2974
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, clamp(1.75rem, 9vw, 2.75rem)); }",
|
|
2847
2975
|
" .gem-section__grid { grid-template-columns: minmax(0, 1fr); }",
|
|
2848
2976
|
" .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); }",
|
|
2849
2977
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-main__inner { grid-template-columns: minmax(0, 1fr); }",
|
|
@@ -2863,7 +2991,7 @@ function createSectionCss() {
|
|
|
2863
2991
|
function createDawnParityCss() {
|
|
2864
2992
|
return [
|
|
2865
2993
|
"/* Dawn parity profile: locked default baseline; morph presets must opt out before applying Gem visual identity. */",
|
|
2866
|
-
":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; }",
|
|
2994
|
+
":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); }",
|
|
2867
2995
|
".utility-bar, .announcement-bar, .utility-bar__grid { min-height: 38px; }",
|
|
2868
2996
|
".announcement-bar__message { margin: 0; min-height: 38px; padding-block: 10px; font-size: 0.75rem; font-weight: 400; letter-spacing: 0.04em; }",
|
|
2869
2997
|
// The pair of rules here used to blank the real message (font-size: 0) and inject a FIXED
|
|
@@ -2939,7 +3067,12 @@ function createDawnParityCss() {
|
|
|
2939
3067
|
".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); }",
|
|
2940
3068
|
".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; }",
|
|
2941
3069
|
".gem-hero-banner .gem-section__content { max-width: 78rem; margin-inline: auto; }",
|
|
2942
|
-
".gem-hero-banner h1, .gem-hero-banner h2, .gem-section__heading { margin: 0 0 1.5rem; font-
|
|
3070
|
+
".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; }",
|
|
3071
|
+
// Font size has ONE owner per selector (one-owner-per-selector.test.ts): section headings take H2 of the plan's ramp,
|
|
3072
|
+
// the hero's own title H1 (tokens.ts GEM_HEADING_SIZE_CSS_VARS). Same position and specificity as the shared rule above,
|
|
3073
|
+
// so with no ramp both resolve to the clamp that rule used to set.
|
|
3074
|
+
".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)))); }",
|
|
3075
|
+
".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)))); }",
|
|
2943
3076
|
".gem-section__eyebrow { display: none; }",
|
|
2944
3077
|
".gem-hero-banner--compact-collection { min-height: auto; display: block; text-align: left; padding-block: 25px; }",
|
|
2945
3078
|
".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; }",
|
|
@@ -2954,7 +3087,13 @@ function createDawnParityCss() {
|
|
|
2954
3087
|
".gem-rich-text--dawn-intro .gem-section__body { width: 100%; max-width: none; margin: 20px 0 0; font-size: 16px; line-height: 28.8px; }",
|
|
2955
3088
|
".gem-rich-text--dawn-intro .gem-section__body p { margin: 0; }",
|
|
2956
3089
|
".gem-rich-text--dawn-intro .gem-rich-text__blocks { display: block; width: 100%; }",
|
|
2957
|
-
|
|
3090
|
+
// Dawn's square, regular-weight button stays the default; the button tokens (radius.button, typography.buttonWeight)
|
|
3091
|
+
// override it, or this rule (same specificity, later) would pin every button square at 400 whatever the plan says.
|
|
3092
|
+
// The weight sits in its own rule that skips link buttons, which keep today's 400 here: the rule above it is what
|
|
3093
|
+
// overrides `.gem-button--link { font-weight: inherit }` in this profile, and a weight token must not reach a link.
|
|
3094
|
+
// `:where()` keeps both at one class of specificity, so a section's own CTA rule still wins.
|
|
3095
|
+
".gem-button { min-width: 12rem; border-radius: var(--gem-radius-button, 0); font-weight: 400; box-shadow: none; }",
|
|
3096
|
+
".gem-button:where(:not(.gem-button--link)) { font-weight: var(--gem-font-button-weight, 400); }",
|
|
2958
3097
|
".gem-product-card-wrapper { display: block; min-width: 0; }",
|
|
2959
3098
|
".gem-card, .gem-product-card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }",
|
|
2960
3099
|
".gem-product-card__media, .gem-media { border: 0; border-radius: 0; box-shadow: none; background: rgb(244 244 244); }",
|
|
@@ -3150,7 +3289,9 @@ function createDawnParityMobileCss() {
|
|
|
3150
3289
|
" .gem-product-card__content { padding-top: 8px; gap: 4px; }",
|
|
3151
3290
|
" .gem-product-card__title { font-size: 15px; line-height: 24px; }",
|
|
3152
3291
|
" .gem-product-card__price, .gem-product-card__price .gem-price { font-size: 16px; line-height: 24px; }",
|
|
3153
|
-
|
|
3292
|
+
// Fallback = the profile's own phone heading, declared once as --gem-heading-phone-cap on the :root above.
|
|
3293
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3294
|
+
" .gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3154
3295
|
" .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)); }",
|
|
3155
3296
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] > .gem-section__inner { width: calc(100% - 30px); }",
|
|
3156
3297
|
" .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; }",
|
|
@@ -4193,7 +4334,10 @@ function gemFrameworkPublicCssVariables() {
|
|
|
4193
4334
|
createPrimitiveCss(),
|
|
4194
4335
|
String(createSectionCss()),
|
|
4195
4336
|
String(createShellCss()),
|
|
4196
|
-
String(createGemSectionRuntimeContractCss())
|
|
4337
|
+
String(createGemSectionRuntimeContractCss()),
|
|
4338
|
+
// The button case rule only exists when a token sets it; a section may still read its variables (a CTA rule
|
|
4339
|
+
// beats the zero-specificity rule, so opting in means `text-transform: var(--gem-font-button-transform, none)`).
|
|
4340
|
+
gemButtonCaseCss({ typography: { buttonTransform: "none", buttonLetterSpacing: "normal" } })
|
|
4197
4341
|
].join("\n").replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
4198
4342
|
frameworkPublicVariables = gemPublicCssVariablesFrom(css);
|
|
4199
4343
|
}
|
package/dist/sections.css
CHANGED
|
@@ -643,8 +643,8 @@ sticky-header.header-wrapper {
|
|
|
643
643
|
.gem-section__heading {
|
|
644
644
|
margin: 0;
|
|
645
645
|
font-family: var(--gem-font-heading);
|
|
646
|
-
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)));
|
|
647
|
-
line-height: 1.04;
|
|
646
|
+
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))));
|
|
647
|
+
line-height: var(--gem-font-heading-line-height, 1.04);
|
|
648
648
|
}
|
|
649
649
|
.gem-section__body {
|
|
650
650
|
margin: 0;
|
|
@@ -761,20 +761,20 @@ sticky-header.header-wrapper {
|
|
|
761
761
|
}
|
|
762
762
|
}
|
|
763
763
|
@media (max-width: 760px) {
|
|
764
|
-
.gem-section { padding-block: calc(var(--gem-layout-section, 64px) * 0.72); }
|
|
764
|
+
.gem-section { padding-block: var(--gem-layout-section-mobile, calc(var(--gem-layout-section, 64px) * 0.72)); }
|
|
765
765
|
.gem-section__inner, .gem-container { width: min(100% - 32px, var(--gem-layout-page, 1180px)); }
|
|
766
|
-
.gem-section__heading { font-size: clamp(1.75rem, 9vw, 2.75rem); }
|
|
766
|
+
.gem-section__heading { font-size: var(--gem-section-heading-size-mobile, clamp(1.75rem, 9vw, 2.75rem)); }
|
|
767
767
|
.gem-section__grid { grid-template-columns: minmax(0, 1fr); }
|
|
768
768
|
.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); }
|
|
769
769
|
.gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-main__inner { grid-template-columns: minmax(0, 1fr); }
|
|
770
770
|
.gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-main__summary { position: static; }
|
|
771
771
|
.gem-section:is([data-gem-composition-mode='slideshow'], [data-gem-composition-mode='video-led']) .gem-section__inner { min-block-size: min(62vh, 560px); }
|
|
772
772
|
}
|
|
773
|
-
:where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; letter-spacing: 0.12em; }
|
|
774
|
-
: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; }
|
|
773
|
+
:where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; font-size: var(--gem-text-sm); letter-spacing: 0.12em; }
|
|
774
|
+
: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; }
|
|
775
775
|
: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); }
|
|
776
776
|
@media (max-width: 760px) {
|
|
777
|
-
:where(.gem-section[data-gem-heading='section']) .gem-section__heading { font-size: min(var(--gem-h2), clamp(1.75rem, 9vw, 2.75rem)); }
|
|
777
|
+
: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))); }
|
|
778
778
|
}
|
|
779
779
|
.gem-row:where(:not([hidden])) { --gem-row-auto-sm: repeat(var(--gem-row-cols-sm), minmax(0, 1fr)); --gem-row-auto-md: repeat(var(--gem-row-cols-md), minmax(0, 1fr)); --gem-row-auto-lg: repeat(var(--gem-row-cols), minmax(0, 1fr)); display: grid; grid-template-columns: var(--gem-row-tracks-sm, var(--gem-row-auto-sm, minmax(0, 1fr))); }
|
|
780
780
|
:where(.gem-row > *) { min-width: 0; --gem-row-cols: initial; --gem-row-cols-md: initial; --gem-row-cols-sm: initial; --gem-row-tracks: initial; --gem-row-tracks-md: initial; --gem-row-tracks-sm: initial; --gem-row-gap: initial; --gem-row-gap-y: initial; }
|
|
@@ -782,7 +782,7 @@ sticky-header.header-wrapper {
|
|
|
782
782
|
@media (min-width: 990px) { .gem-row { grid-template-columns: var(--gem-row-tracks, var(--gem-row-auto-lg, repeat(2, minmax(0, 1fr)))); } }
|
|
783
783
|
|
|
784
784
|
/* Dawn parity profile: locked default baseline; morph presets must opt out before applying Gem visual identity. */
|
|
785
|
-
: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; }
|
|
785
|
+
: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); }
|
|
786
786
|
.utility-bar, .announcement-bar, .utility-bar__grid { min-height: 38px; }
|
|
787
787
|
.announcement-bar__message { margin: 0; min-height: 38px; padding-block: 10px; font-size: 0.75rem; font-weight: 400; letter-spacing: 0.04em; }
|
|
788
788
|
.announcement-bar__message span { font-size: 0.75rem; }
|
|
@@ -835,7 +835,9 @@ main > .gem-section:first-child { padding-top: 0; }
|
|
|
835
835
|
.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); }
|
|
836
836
|
.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; }
|
|
837
837
|
.gem-hero-banner .gem-section__content { max-width: 78rem; margin-inline: auto; }
|
|
838
|
-
.gem-hero-banner h1, .gem-hero-banner h2, .gem-section__heading { margin: 0 0 1.5rem; font-
|
|
838
|
+
.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; }
|
|
839
|
+
.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)))); }
|
|
840
|
+
.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)))); }
|
|
839
841
|
.gem-section__eyebrow { display: none; }
|
|
840
842
|
.gem-hero-banner--compact-collection { min-height: auto; display: block; text-align: left; padding-block: 25px; }
|
|
841
843
|
.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; }
|
|
@@ -850,7 +852,8 @@ main > .gem-section:first-child { padding-top: 0; }
|
|
|
850
852
|
.gem-rich-text--dawn-intro .gem-section__body { width: 100%; max-width: none; margin: 20px 0 0; font-size: 16px; line-height: 28.8px; }
|
|
851
853
|
.gem-rich-text--dawn-intro .gem-section__body p { margin: 0; }
|
|
852
854
|
.gem-rich-text--dawn-intro .gem-rich-text__blocks { display: block; width: 100%; }
|
|
853
|
-
.gem-button { min-width: 12rem; border-radius: 0; font-weight: 400; box-shadow: none; }
|
|
855
|
+
.gem-button { min-width: 12rem; border-radius: var(--gem-radius-button, 0); font-weight: 400; box-shadow: none; }
|
|
856
|
+
.gem-button:where(:not(.gem-button--link)) { font-weight: var(--gem-font-button-weight, 400); }
|
|
854
857
|
.gem-product-card-wrapper { display: block; min-width: 0; }
|
|
855
858
|
.gem-card, .gem-product-card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }
|
|
856
859
|
.gem-product-card__media, .gem-media { border: 0; border-radius: 0; box-shadow: none; background: rgb(244 244 244); }
|
|
@@ -989,8 +992,8 @@ main > .gem-section:first-child { padding-top: 0; }
|
|
|
989
992
|
.gem-shell-footer__payment-fallback { display: inline-grid; place-items: center; min-width: 38px; height: 24px; border: 1px solid rgb(18 18 18 / 12%); border-radius: 3px; background: white; color: rgb(18 18 18 / 72%); font-size: 8px; line-height: 1; }
|
|
990
993
|
.gem-shell-footer__localization { order: -1; flex: 0 0 100%; width: 100%; }
|
|
991
994
|
.gem-shell-footer__localization .gem-select, .gem-shell-footer__localization > .gem-select { display: inline-flex; align-items: center; min-width: 210px; min-height: 45px; border: 1px solid rgb(18 18 18 / 55%); border-radius: 0; background: transparent; padding: 10px 36px 10px 12px; color: rgb(18 18 18); font-size: 13px; line-height: 18px; }
|
|
992
|
-
:where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; letter-spacing: 0.12em; }
|
|
993
|
-
: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; }
|
|
995
|
+
:where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; font-size: var(--gem-text-sm); letter-spacing: 0.12em; }
|
|
996
|
+
: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; }
|
|
994
997
|
: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); }
|
|
995
998
|
@media (max-width: 760px) {
|
|
996
999
|
:root { --gem-layout-gutter: 24px; --gem-layout-section: 36px; }
|
|
@@ -1027,7 +1030,8 @@ main > .gem-section:first-child { padding-top: 0; }
|
|
|
1027
1030
|
.gem-product-card__content { padding-top: 8px; gap: 4px; }
|
|
1028
1031
|
.gem-product-card__title { font-size: 15px; line-height: 24px; }
|
|
1029
1032
|
.gem-product-card__price, .gem-product-card__price .gem-price { font-size: 16px; line-height: 24px; }
|
|
1030
|
-
.gem-
|
|
1033
|
+
.gem-section__heading { font-size: var(--gem-section-heading-size-mobile, var(--gem-heading-phone-cap)); }
|
|
1034
|
+
.gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size-mobile, var(--gem-heading-phone-cap)); }
|
|
1031
1035
|
.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)); }
|
|
1032
1036
|
.gem-product-main[data-gem-product-main-layout='dawn-main'] > .gem-section__inner { width: calc(100% - 30px); }
|
|
1033
1037
|
.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; }
|
|
@@ -1108,7 +1112,7 @@ main > .gem-section:first-child { padding-top: 0; }
|
|
|
1108
1112
|
.gem-hero-video--dawn-video > .gem-section__inner, .gem-hero-video--dawn-video .gem-hero-video__inner, .gem-hero-video--dawn-video .gem-hero-video__media { height: 212px; min-height: 212px; }
|
|
1109
1113
|
.gem-advanced-list--feature-grid { height: 420px; min-height: 420px; padding-block: 36px; }
|
|
1110
1114
|
.gem-advanced-list--feature-grid .gem-section__grid { grid-template-columns: 1fr; }
|
|
1111
|
-
:where(.gem-section[data-gem-heading='section']) .gem-section__heading { font-size: min(var(--gem-h2), clamp(1.75rem, 9vw, 2.75rem)); }
|
|
1115
|
+
: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))); }
|
|
1112
1116
|
}
|
|
1113
1117
|
.gem-row:where(:not([hidden])) { --gem-row-auto-sm: repeat(var(--gem-row-cols-sm), minmax(0, 1fr)); --gem-row-auto-md: repeat(var(--gem-row-cols-md), minmax(0, 1fr)); --gem-row-auto-lg: repeat(var(--gem-row-cols), minmax(0, 1fr)); display: grid; grid-template-columns: var(--gem-row-tracks-sm, var(--gem-row-auto-sm, minmax(0, 1fr))); }
|
|
1114
1118
|
:where(.gem-row > *) { min-width: 0; --gem-row-cols: initial; --gem-row-cols-md: initial; --gem-row-cols-sm: initial; --gem-row-tracks: initial; --gem-row-tracks-md: initial; --gem-row-tracks-sm: initial; --gem-row-gap: initial; --gem-row-gap-y: initial; }
|
package/dist/tokens.css
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
--gem-color-accent-text: #ffffff;
|
|
8
8
|
--gem-color-border: #d2d5d8;
|
|
9
9
|
--gem-color-success: #008060;
|
|
10
|
-
--gem-color-warning: #
|
|
10
|
+
--gem-color-warning: #9a661a;
|
|
11
11
|
--gem-color-danger: #d72c0d;
|
|
12
12
|
--gem-font-body: Inter, system-ui, sans-serif;
|
|
13
13
|
--gem-font-heading: Inter, system-ui, sans-serif;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/gem-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "gem-framework của GemTheme (CSS, runtime, token, bộ kiểm) cộng đúng bộ kiểm section gemtheme dùng khi duyệt. Build từ gemtheme, không sửa ở đây.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|