@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/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",
|
|
@@ -194,17 +307,21 @@ var defaultGemTokens = {
|
|
|
194
307
|
}
|
|
195
308
|
};
|
|
196
309
|
function createGemTokenCss(tokens, selector = ":root") {
|
|
310
|
+
const button = readButtonTokens(tokens);
|
|
311
|
+
const phone = readPhoneTokens(tokens);
|
|
312
|
+
const spacing = desktopEntries("spacing", tokens.spacing);
|
|
197
313
|
const declarations = [
|
|
198
314
|
...toCssVars("color", tokens.color),
|
|
199
|
-
...toCssVars("font", Object.fromEntries(Object.entries(tokens.typography ?? {}).filter(([k]) => !isRampKey(k)))),
|
|
315
|
+
...toCssVars("font", Object.fromEntries(Object.entries(button.groups.typography ?? tokens.typography ?? {}).filter(([k, v]) => !isRampKey(k) && !badLineHeight(k, v)))),
|
|
200
316
|
// Space ramp is scaled by the global density knob --gem-style-space (default 1),
|
|
201
317
|
// so a style pack can make the WHOLE storefront airier or denser without touching
|
|
202
318
|
// any section — every section already consumes --gem-space-*. Knob unset → identical.
|
|
203
|
-
|
|
204
|
-
...
|
|
319
|
+
// Phone keys (`3xlMobile`) are not desktop variables: they go to the phone block below (GEM_PHONE_TOKEN_TARGETS).
|
|
320
|
+
...toScaledCssVars("space", spacing, "--gem-style-space"),
|
|
321
|
+
...gemDerivedSpaceCssVars(spacing),
|
|
205
322
|
// Radius ramp scaled by --gem-style-radius (default 1): a 'sharp' style sets it to
|
|
206
323
|
// 0 (square everything), 'pill' rounds further — across every section, no edits.
|
|
207
|
-
...toScaledCssVars("radius", tokens.radius, "--gem-style-radius"),
|
|
324
|
+
...toScaledCssVars("radius", button.groups.radius ?? tokens.radius, "--gem-style-radius"),
|
|
208
325
|
// Border widths get the --gem-style-border knob injected into their leading length,
|
|
209
326
|
// whatever value the plan provides (e.g. "1px solid …" → "calc(1px * var(...)) solid …").
|
|
210
327
|
...toBorderCssVars("border", tokens.border, "--gem-style-border"),
|
|
@@ -213,7 +330,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
213
330
|
// and marquee primitives consume; unrecognised keys fall back to the --gem-motion-* namespace,
|
|
214
331
|
// so duration/easing keep emitting --gem-motion-duration/easing exactly as before (back-compat).
|
|
215
332
|
...toMotionCssVars(tokens.motion),
|
|
216
|
-
...toCssVars("layout", tokens.layout),
|
|
333
|
+
...toCssVars("layout", desktopEntries("layout", tokens.layout)),
|
|
334
|
+
...phone.layout.section ? [`--gem-layout-section-mobile: ${phone.layout.section};`] : [],
|
|
217
335
|
...toCssVars("component", tokens.component),
|
|
218
336
|
...toCssVars("morphology", tokens.morphology),
|
|
219
337
|
// THE GOVERNED TYPE RAMP — --gem-h1..h6 / --gem-body / --gem-text-sm.
|
|
@@ -223,6 +341,8 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
223
341
|
// variables that resolved to nothing. One modular scale, retuned by overriding --gem-type-base
|
|
224
342
|
// or --gem-type-scale (see gemTypeRampCssVars).
|
|
225
343
|
...gemTypeRampCssVars(tokens.typography),
|
|
344
|
+
// Heading sizes from the ramp, only for a plan that chose a ramp (GEM_HEADING_SIZE_CSS_VARS).
|
|
345
|
+
...typeRampValue(tokens.typography, "base") || typeRampValue(tokens.typography, "scale") ? GEM_HEADING_SIZE_CSS_VARS : [],
|
|
226
346
|
// Focus ring (gemfactory request #3, 28/09/2026): 11 sections wrote `outline: 2px solid
|
|
227
347
|
// var(--gem-color-accent)` by hand. Not a token GROUP (a group would name it --gem-border-focus-ring
|
|
228
348
|
// and a required key would fail every stored plan/preset without it), so it is emitted here like the
|
|
@@ -232,16 +352,25 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
232
352
|
GEM_FOCUS_OFFSET_CSS_VAR
|
|
233
353
|
];
|
|
234
354
|
const formBlock = gemFormCssBlock(tokens.form);
|
|
235
|
-
const
|
|
355
|
+
const buttonCase = gemButtonCaseCss(tokens, selector);
|
|
356
|
+
const mobile = gemMobileTokenCssVars(tokens, phone);
|
|
236
357
|
return [
|
|
237
358
|
`${selector} {`,
|
|
238
359
|
...declarations.map((line) => ` ${line}`),
|
|
239
360
|
"}",
|
|
240
|
-
...
|
|
241
|
-
...formBlock ? [formBlock] : []
|
|
361
|
+
...mobile.length ? [`@media (max-width: ${GEM_TYPE_MOBILE_MAX}) {`, ` ${selector} {`, ...mobile.map((line) => ` ${line}`), " }", "}"] : [],
|
|
362
|
+
...formBlock ? [formBlock] : [],
|
|
363
|
+
...buttonCase ? [buttonCase] : []
|
|
242
364
|
].join("\n");
|
|
243
365
|
}
|
|
244
|
-
|
|
366
|
+
function gemMobileTokenCssVars(tokens, phone = readPhoneTokens(tokens)) {
|
|
367
|
+
const baseMobile = typeRampValue(tokens.typography, "baseMobile");
|
|
368
|
+
return [
|
|
369
|
+
...baseMobile ? [`--gem-type-base: ${baseMobile};`] : [],
|
|
370
|
+
...toScaledCssVars("space", phone.spacing, "--gem-style-space"),
|
|
371
|
+
...toCssVars("layout", phone.layout)
|
|
372
|
+
];
|
|
373
|
+
}
|
|
245
374
|
var rampKey = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
246
375
|
var isRampKey = (k) => GEM_TYPE_RAMP_KEYS.some((r) => rampKey(r) === rampKey(k));
|
|
247
376
|
var LENGTH = /^\d*\.?\d+(px|rem|em)$/;
|
|
@@ -890,7 +1019,6 @@ var GEM_AMBIENT_CSS_LINES = [
|
|
|
890
1019
|
"@media (prefers-reduced-motion: reduce) {",
|
|
891
1020
|
" [data-gem-ambient] { --gem-ambient-amp: 2px; animation-duration: 12s; }",
|
|
892
1021
|
" [data-gem-reveal] { transition: none; opacity: 1; transform: none; }",
|
|
893
|
-
" .gem-marquee__track { animation-duration: calc(var(--gem-marquee-duration, 40s) * 2.5); }",
|
|
894
1022
|
"}"
|
|
895
1023
|
];
|
|
896
1024
|
var GEM_AMBIENT_CSS = GEM_AMBIENT_CSS_LINES.join("\n");
|
|
@@ -1073,9 +1201,13 @@ function createPrimitiveCss() {
|
|
|
1073
1201
|
" justify-content: center;",
|
|
1074
1202
|
" min-height: var(--gem-component-input-height, 44px);",
|
|
1075
1203
|
" border: var(--gem-button-border-width, 1px) solid var(--gem-color-border);",
|
|
1076
|
-
|
|
1204
|
+
// Button tokens (GEM_BUTTON_TOKEN_KEYS, optional): unset, radius and weight fall back to exactly what this rule always
|
|
1205
|
+
// drew. --gem-radius-button is the button's own radius, so a pill button does not turn every card into a pill
|
|
1206
|
+
// (radius-md is shared). Case and tracking are NOT declared here: <button> gets `none` / `normal` from the UA sheet
|
|
1207
|
+
// while <a> inherits, so createGemTokenCss adds them only when a token sets them (gemButtonCaseCss).
|
|
1208
|
+
" border-radius: var(--gem-radius-button, var(--gem-radius-md));",
|
|
1077
1209
|
" padding: var(--gem-component-button-padding-block, 12px) var(--gem-component-button-padding-inline, 18px);",
|
|
1078
|
-
" font-weight: 700;",
|
|
1210
|
+
" font-weight: var(--gem-font-button-weight, 700);",
|
|
1079
1211
|
" text-decoration: none;",
|
|
1080
1212
|
" transition: background var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease), color var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease);",
|
|
1081
1213
|
"}",
|
|
@@ -1338,28 +1470,6 @@ function createPrimitiveCss() {
|
|
|
1338
1470
|
" border-radius: var(--gem-radius-md);",
|
|
1339
1471
|
" background: color-mix(in srgb, var(--gem-color-text) 8%, var(--gem-color-background));",
|
|
1340
1472
|
"}",
|
|
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
1473
|
".gem-carousel {",
|
|
1364
1474
|
" display: grid;",
|
|
1365
1475
|
" gap: var(--gem-space-md);",
|
|
@@ -1495,9 +1605,20 @@ function createPrimitiveCss() {
|
|
|
1495
1605
|
" overflow: hidden;",
|
|
1496
1606
|
" color: var(--gem-color-accent);",
|
|
1497
1607
|
"}",
|
|
1498
|
-
// Marquee primitive
|
|
1499
|
-
//
|
|
1500
|
-
|
|
1608
|
+
// Marquee primitive. CSS-only and deliberately so (no JS to init, works on every render path).
|
|
1609
|
+
// Contract: `.gem-marquee > .gem-marquee__viewport > .gem-marquee__track` TWICE (the second copy
|
|
1610
|
+
// aria-hidden); each track is at least the viewport wide and moves -100% of itself, so the second
|
|
1611
|
+
// copy is what makes the loop seamless. gemcapture's clone kit and GemFactory build exactly this.
|
|
1612
|
+
// ONE definition (0.9.11): until then a second, earlier one (gemtheme #124: one track with its
|
|
1613
|
+
// children doubled, -50%) sat above this and lost every property both set, so its contract never
|
|
1614
|
+
// worked. What it set that still took effect (the border, will-change, pause on hover of the whole
|
|
1615
|
+
// marquee, the data-attribute reverse) is kept here, so a marquee built to either contract computes
|
|
1616
|
+
// the same style as before (Chromium, 08/10/2026). The one exception would be a track that also
|
|
1617
|
+
// carries data-gem-ambient, which nothing renders.
|
|
1618
|
+
".gem-marquee {",
|
|
1619
|
+
" overflow: hidden;",
|
|
1620
|
+
" border-block: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1621
|
+
"}",
|
|
1501
1622
|
".gem-marquee__viewport { display: flex; overflow: hidden; }",
|
|
1502
1623
|
".gem-marquee__track {",
|
|
1503
1624
|
" flex: 0 0 auto;",
|
|
@@ -1508,9 +1629,12 @@ function createPrimitiveCss() {
|
|
|
1508
1629
|
" gap: var(--gem-space-lg);",
|
|
1509
1630
|
" white-space: nowrap;",
|
|
1510
1631
|
" animation: gem-marquee var(--gem-marquee-duration, 24s) linear infinite;",
|
|
1632
|
+
" will-change: transform;",
|
|
1511
1633
|
"}",
|
|
1512
|
-
|
|
1513
|
-
".gem-marquee
|
|
1634
|
+
// Pause-on-hover lets a shopper read a badge/logo they want to inspect.
|
|
1635
|
+
".gem-marquee:hover .gem-marquee__track, .gem-marquee__viewport:hover .gem-marquee__track { animation-play-state: paused; }",
|
|
1636
|
+
// Opt-in reverse direction (e.g. a second row scrolling the other way).
|
|
1637
|
+
".gem-marquee--reverse .gem-marquee__track, .gem-marquee[data-gem-marquee-reverse] .gem-marquee__track { animation-direction: reverse; }",
|
|
1514
1638
|
"@keyframes gem-marquee { from { transform: translateX(0); } to { transform: translateX(-100%); } }",
|
|
1515
1639
|
"@media (prefers-reduced-motion: reduce) {",
|
|
1516
1640
|
" .gem-marquee__track { animation: none; }",
|
|
@@ -2528,10 +2652,10 @@ function createGemSectionRuntimeContractCss(selector = "[data-gem-section-librar
|
|
|
2528
2652
|
"}"
|
|
2529
2653
|
].join("\n");
|
|
2530
2654
|
}
|
|
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; }";
|
|
2655
|
+
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; }";
|
|
2656
|
+
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
2657
|
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)); }";
|
|
2658
|
+
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
2659
|
function createSectionCss() {
|
|
2536
2660
|
return [
|
|
2537
2661
|
".gem-section {",
|
|
@@ -2723,8 +2847,8 @@ function createSectionCss() {
|
|
|
2723
2847
|
".gem-section__heading {",
|
|
2724
2848
|
" margin: 0;",
|
|
2725
2849
|
" 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;",
|
|
2850
|
+
" 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))));",
|
|
2851
|
+
" line-height: var(--gem-font-heading-line-height, 1.04);",
|
|
2728
2852
|
"}",
|
|
2729
2853
|
".gem-section__body {",
|
|
2730
2854
|
" margin: 0;",
|
|
@@ -2841,9 +2965,10 @@ function createSectionCss() {
|
|
|
2841
2965
|
" }",
|
|
2842
2966
|
"}",
|
|
2843
2967
|
"@media (max-width: 760px) {",
|
|
2844
|
-
|
|
2968
|
+
// A declared layout.sectionMobile is final (tokens.ts GEM_PHONE_TOKEN_TARGETS); otherwise the desktop value × 0.72.
|
|
2969
|
+
" .gem-section { padding-block: var(--gem-layout-section-mobile, calc(var(--gem-layout-section, 64px) * 0.72)); }",
|
|
2845
2970
|
" .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); }",
|
|
2971
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, clamp(1.75rem, 9vw, 2.75rem)); }",
|
|
2847
2972
|
" .gem-section__grid { grid-template-columns: minmax(0, 1fr); }",
|
|
2848
2973
|
" .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
2974
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-main__inner { grid-template-columns: minmax(0, 1fr); }",
|
|
@@ -2863,7 +2988,7 @@ function createSectionCss() {
|
|
|
2863
2988
|
function createDawnParityCss() {
|
|
2864
2989
|
return [
|
|
2865
2990
|
"/* 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; }",
|
|
2991
|
+
":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
2992
|
".utility-bar, .announcement-bar, .utility-bar__grid { min-height: 38px; }",
|
|
2868
2993
|
".announcement-bar__message { margin: 0; min-height: 38px; padding-block: 10px; font-size: 0.75rem; font-weight: 400; letter-spacing: 0.04em; }",
|
|
2869
2994
|
// The pair of rules here used to blank the real message (font-size: 0) and inject a FIXED
|
|
@@ -2939,7 +3064,12 @@ function createDawnParityCss() {
|
|
|
2939
3064
|
".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
3065
|
".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
3066
|
".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-
|
|
3067
|
+
".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; }",
|
|
3068
|
+
// Font size has ONE owner per selector (one-owner-per-selector.test.ts): section headings take H2 of the plan's ramp,
|
|
3069
|
+
// the hero's own title H1 (tokens.ts GEM_HEADING_SIZE_CSS_VARS). Same position and specificity as the shared rule above,
|
|
3070
|
+
// so with no ramp both resolve to the clamp that rule used to set.
|
|
3071
|
+
".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)))); }",
|
|
3072
|
+
".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
3073
|
".gem-section__eyebrow { display: none; }",
|
|
2944
3074
|
".gem-hero-banner--compact-collection { min-height: auto; display: block; text-align: left; padding-block: 25px; }",
|
|
2945
3075
|
".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 +3084,13 @@ function createDawnParityCss() {
|
|
|
2954
3084
|
".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
3085
|
".gem-rich-text--dawn-intro .gem-section__body p { margin: 0; }",
|
|
2956
3086
|
".gem-rich-text--dawn-intro .gem-rich-text__blocks { display: block; width: 100%; }",
|
|
2957
|
-
|
|
3087
|
+
// Dawn's square, regular-weight button stays the default; the button tokens (radius.button, typography.buttonWeight)
|
|
3088
|
+
// override it, or this rule (same specificity, later) would pin every button square at 400 whatever the plan says.
|
|
3089
|
+
// The weight sits in its own rule that skips link buttons, which keep today's 400 here: the rule above it is what
|
|
3090
|
+
// overrides `.gem-button--link { font-weight: inherit }` in this profile, and a weight token must not reach a link.
|
|
3091
|
+
// `:where()` keeps both at one class of specificity, so a section's own CTA rule still wins.
|
|
3092
|
+
".gem-button { min-width: 12rem; border-radius: var(--gem-radius-button, 0); font-weight: 400; box-shadow: none; }",
|
|
3093
|
+
".gem-button:where(:not(.gem-button--link)) { font-weight: var(--gem-font-button-weight, 400); }",
|
|
2958
3094
|
".gem-product-card-wrapper { display: block; min-width: 0; }",
|
|
2959
3095
|
".gem-card, .gem-product-card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }",
|
|
2960
3096
|
".gem-product-card__media, .gem-media { border: 0; border-radius: 0; box-shadow: none; background: rgb(244 244 244); }",
|
|
@@ -3150,7 +3286,9 @@ function createDawnParityMobileCss() {
|
|
|
3150
3286
|
" .gem-product-card__content { padding-top: 8px; gap: 4px; }",
|
|
3151
3287
|
" .gem-product-card__title { font-size: 15px; line-height: 24px; }",
|
|
3152
3288
|
" .gem-product-card__price, .gem-product-card__price .gem-price { font-size: 16px; line-height: 24px; }",
|
|
3153
|
-
|
|
3289
|
+
// Fallback = the profile's own phone heading, declared once as --gem-heading-phone-cap on the :root above.
|
|
3290
|
+
" .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3291
|
+
" .gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size-mobile, var(--gem-heading-phone-cap)); }",
|
|
3154
3292
|
" .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
3293
|
" .gem-product-main[data-gem-product-main-layout='dawn-main'] > .gem-section__inner { width: calc(100% - 30px); }",
|
|
3156
3294
|
" .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 +4331,10 @@ function gemFrameworkPublicCssVariables() {
|
|
|
4193
4331
|
createPrimitiveCss(),
|
|
4194
4332
|
String(createSectionCss()),
|
|
4195
4333
|
String(createShellCss()),
|
|
4196
|
-
String(createGemSectionRuntimeContractCss())
|
|
4334
|
+
String(createGemSectionRuntimeContractCss()),
|
|
4335
|
+
// The button case rule only exists when a token sets it; a section may still read its variables (a CTA rule
|
|
4336
|
+
// beats the zero-specificity rule, so opting in means `text-transform: var(--gem-font-button-transform, none)`).
|
|
4337
|
+
gemButtonCaseCss({ typography: { buttonTransform: "none", buttonLetterSpacing: "normal" } })
|
|
4197
4338
|
].join("\n").replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
4198
4339
|
frameworkPublicVariables = gemPublicCssVariablesFrom(css);
|
|
4199
4340
|
}
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/gem-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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",
|