@gemboss/gem-framework 0.12.0 → 0.13.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 +17 -0
- package/dist/SOURCE.json +3 -3
- package/dist/gem-framework.css +10 -0
- package/dist/index.js +64 -9
- package/dist/primitives.css +10 -0
- package/dist/section-gate.js +41 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,23 @@ Từ 0.8.2 mỗi module có `.d.ts` cạnh nó (`mount(root?: ParentNode | null,
|
|
|
93
93
|
|
|
94
94
|
## Nhật ký thay đổi
|
|
95
95
|
|
|
96
|
+
- **0.13.0** (11/10/2026): kiểu nút **`gradient`** (`plan.global_settings.button_style`, `<body data-gem-button-style="gradient">`).
|
|
97
|
+
GemFactory Global Style đo 339 trang store hàng đầu: nút mua chính là gradient ở 8,4% (10,6% trang landing), 16/21 cái đo
|
|
98
|
+
được là MỘT tông đi từ sáng sang sẫm, và 14/21 có chữ dưới 4,5:1. Đầu kia của gradient là màu nhấn dời 25%
|
|
99
|
+
(`GEM_BUTTON_GRADIENT_SHIFT`) RA XA chữ nút: về đen khi màu nhấn tối hơn chữ, về trắng khi sáng hơn, nên chữ không bao giờ
|
|
100
|
+
kém tương phản hơn trên nền đặc. Viết bằng CSS (`color-mix` + `rgb(from …)`), đọc `--gem-color-accent` / `-accent-text`,
|
|
101
|
+
nên nút trong băng tối / băng accent theo bộ màu của băng; nằm trong `@supports`, trình duyệt không có cú pháp màu tương đối
|
|
102
|
+
giữ nút đặc như cũ. Bỏ qua nút link và secondary. Export mới: `gemButtonGradientEnd` (cùng phép tính bằng JS, cho phép đo),
|
|
103
|
+
`gemButtonGradientEndCss`, `GEM_BUTTON_GRADIENT_SHIFT`, `GEM_BUTTON_GRADIENT_PIVOT`. Schema `@gemtheme/schemas`: một
|
|
104
|
+
`buttonStyleSchema` cho cả ba chỗ trước đây tự liệt kê (cài đặt theme, design system plan, preset morph). Store không chọn
|
|
105
|
+
gradient không đổi gì, nên không cần edition mới. Biết mà chưa sửa: luật outline / soft cũng chạm nút link (chờ edition sau).
|
|
106
|
+
|
|
107
|
+
- **0.12.1** (10/10/2026): hai ca biên của token nút, review gemsite #839 bắt khi gemsite chép phép đọc này để phát token
|
|
108
|
+
cho store. (1) Bo trỏ một bậc bo khác (`radius.button: var(--gem-radius-pill)`) không bị nhân núm `--gem-style-radius`
|
|
109
|
+
lần hai: bậc được trỏ tới đã nhân rồi (pill 1,4 thành 1,96×). Áp chung cho mọi thang qua `toScaledCssVars`; giá trị số
|
|
110
|
+
và biến ngoài thang (`var(--brand-radius)`) vẫn nhân như cũ. (2) Khai trùng khoá (`buttonTransform` + `button_transform`)
|
|
111
|
+
giữ giá trị HỢP LỆ cuối cùng; bản cũ xoá mất biến trong khi luật in hoa vẫn trỏ tới nó. Không plan, preset hay giá trị mặc
|
|
112
|
+
định nào đang dùng hai dạng này, nên không cần edition mới.
|
|
96
113
|
- **0.12.0** (10/10/2026): **edition** (Chris chốt 10/10/2026). Mỗi store ghim một edition; CSS làm đổi hình store
|
|
97
114
|
không yêu cầu nó thì phát dưới edition kế tiếp thay vì đổi mọi store cùng lúc. Plan mang `framework_edition` (số nguyên,
|
|
98
115
|
thiếu = 1); storefront đặt nó lên `<html>` thành `data-gem-edition`. Luật của edition N viết dưới
|
package/dist/SOURCE.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@gemboss/gem-framework",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gemtheme_sha": "
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"gemtheme_sha": "3e6e73a7466bf54609b5e81abfc3c4d4a06b42d6",
|
|
5
5
|
"gemtheme_dirty": false,
|
|
6
|
-
"framework_version": "0.
|
|
6
|
+
"framework_version": "0.13.0"
|
|
7
7
|
}
|
package/dist/gem-framework.css
CHANGED
|
@@ -214,6 +214,16 @@ a {
|
|
|
214
214
|
border-color: transparent;
|
|
215
215
|
color: var(--gem-color-text);
|
|
216
216
|
}
|
|
217
|
+
@supports (color: rgb(from color-mix(in srgb, red 50%, rgb(from blue calc(255 - r) g b)) clamp(0, (r - 127.5) * 1000, 255) g b)) {
|
|
218
|
+
[data-gem-button-style='gradient'] .gem-button:not(.gem-button--secondary, .gem-button--link),
|
|
219
|
+
[data-gem-button-style='gradient'] .gem-button--primary {
|
|
220
|
+
background-color: var(--gem-color-accent);
|
|
221
|
+
background-image: linear-gradient(135deg, var(--gem-color-accent), color-mix(in srgb, var(--gem-color-accent) 75%, rgb(from color-mix(in srgb, var(--gem-color-accent) 50%, rgb(from var(--gem-color-accent-text, var(--gem-color-surface)) calc(255 - r) calc(255 - g) calc(255 - b))) clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - 127.5) * 1000, 255) clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - 127.5) * 1000, 255) clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - 127.5) * 1000, 255) / 1)));
|
|
222
|
+
background-origin: border-box;
|
|
223
|
+
border-color: transparent;
|
|
224
|
+
color: var(--gem-color-accent-text, var(--gem-color-surface));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
217
227
|
.gem-card {
|
|
218
228
|
border: var(--gem-border-subtle, 1px solid var(--gem-color-border));
|
|
219
229
|
border-radius: var(--gem-card-radius, var(--gem-radius-md));
|
package/dist/index.js
CHANGED
|
@@ -259,9 +259,28 @@ function aaLabelColor(preferred, fill, fallbacks = []) {
|
|
|
259
259
|
if (pass) return pass;
|
|
260
260
|
return candidates.reduce((a, b) => hexContrastRatio(b, ground) > hexContrastRatio(a, ground) ? b : a, preferred);
|
|
261
261
|
}
|
|
262
|
+
var GEM_BUTTON_GRADIENT_SHIFT = 0.25;
|
|
263
|
+
var GEM_BUTTON_GRADIENT_PIVOT = 127.5;
|
|
264
|
+
var weighted = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
265
|
+
var target = (w) => Math.min(255, Math.max(0, (w - GEM_BUTTON_GRADIENT_PIVOT) * 1e3));
|
|
266
|
+
function gemButtonGradientEnd(accent, accentText) {
|
|
267
|
+
const a = normalizeHex(accent);
|
|
268
|
+
const t = normalizeHex(accentText);
|
|
269
|
+
if (!a || !t) return void 0;
|
|
270
|
+
const ch = (h) => [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
271
|
+
const [ar, ag, ab] = ch(a);
|
|
272
|
+
const [tr, tg, tb] = ch(t);
|
|
273
|
+
const gray = Math.round(target(weighted((ar + 255 - tr) / 2, (ag + 255 - tg) / 2, (ab + 255 - tb) / 2))).toString(16).padStart(2, "0");
|
|
274
|
+
return mixHex(a, `#${gray}${gray}${gray}`, GEM_BUTTON_GRADIENT_SHIFT);
|
|
275
|
+
}
|
|
276
|
+
function gemButtonGradientEndCss(accent = "var(--gem-color-accent)", accentText = "var(--gem-color-accent-text, var(--gem-color-surface))") {
|
|
277
|
+
const t = `clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - ${GEM_BUTTON_GRADIENT_PIVOT}) * 1000, 255)`;
|
|
278
|
+
const probe = `color-mix(in srgb, ${accent} 50%, rgb(from ${accentText} calc(255 - r) calc(255 - g) calc(255 - b)))`;
|
|
279
|
+
return `color-mix(in srgb, ${accent} ${100 - GEM_BUTTON_GRADIENT_SHIFT * 100}%, rgb(from ${probe} ${t} ${t} ${t} / 1))`;
|
|
280
|
+
}
|
|
262
281
|
|
|
263
282
|
// ../../packages/gem-framework/src/tokens.ts
|
|
264
|
-
var gemFrameworkVersion = "0.
|
|
283
|
+
var gemFrameworkVersion = "0.13.0";
|
|
265
284
|
var GEM_FOCUS_RING_CSS_VAR = "--gem-focus-ring: 2px solid var(--gem-color-accent);";
|
|
266
285
|
var GEM_TAP_MIN_CSS_VAR = "--gem-tap-min: 44px;";
|
|
267
286
|
var GEM_FOCUS_OFFSET_CSS_VAR = "--gem-focus-offset: 3px;";
|
|
@@ -326,7 +345,7 @@ function readButtonTokens(tokens) {
|
|
|
326
345
|
const key = byNorm.get(buttonKeyNorm(rawKey));
|
|
327
346
|
if (!key) continue;
|
|
328
347
|
copy ??= { ...values };
|
|
329
|
-
delete copy[rawKey];
|
|
348
|
+
if (rawKey !== key) delete copy[rawKey];
|
|
330
349
|
const v = buttonTokenValue(group, key, raw);
|
|
331
350
|
if (v === null) out.invalid.push({ path: `${group}.${rawKey}`, value: raw });
|
|
332
351
|
else {
|
|
@@ -334,6 +353,9 @@ function readButtonTokens(tokens) {
|
|
|
334
353
|
out.values[key] = v;
|
|
335
354
|
}
|
|
336
355
|
}
|
|
356
|
+
if (copy) {
|
|
357
|
+
for (const key of keys) if (!(key in out.values)) delete copy[key];
|
|
358
|
+
}
|
|
337
359
|
if (copy) out.groups[group] = copy;
|
|
338
360
|
}
|
|
339
361
|
return out;
|
|
@@ -461,13 +483,13 @@ function readPhoneTokens(tokens) {
|
|
|
461
483
|
for (const group of ["spacing", "layout"]) {
|
|
462
484
|
const ownGroup = group === "spacing" ? /var\(\s*--gem-space-/i : /var\(\s*--gem-layout-/i;
|
|
463
485
|
for (const [key, raw] of Object.entries(tokens?.[group] ?? {})) {
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
486
|
+
const target2 = gemPhoneTokenTarget(group, key);
|
|
487
|
+
if (target2 == null) continue;
|
|
466
488
|
const v = stripImportant(String(raw ?? "")).trim();
|
|
467
489
|
const kind = gemSpaceMdKind(v);
|
|
468
490
|
if (kind === "absent") continue;
|
|
469
|
-
if (kind === "zero") out[group][
|
|
470
|
-
else if (kind === "length" && !ownGroup.test(v)) out[group][
|
|
491
|
+
if (kind === "zero") out[group][target2] = "0px";
|
|
492
|
+
else if (kind === "length" && !ownGroup.test(v)) out[group][target2] = v;
|
|
471
493
|
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)" });
|
|
472
494
|
}
|
|
473
495
|
}
|
|
@@ -739,7 +761,11 @@ function toScaledCssVars(namespace, values, knob) {
|
|
|
739
761
|
// bọc nó vào GIỮA giá trị — `calc(16px !important * var(...))` — nên custom property lưu
|
|
740
762
|
// nguyên xi, rồi mọi `padding: var(--gem-space-md)` thành invalid ở computed-value và SẬP VỀ 0
|
|
741
763
|
// trên toàn bộ .gem-* section.
|
|
742
|
-
([key, value]) =>
|
|
764
|
+
([key, value]) => {
|
|
765
|
+
const clean = stripImportant(value);
|
|
766
|
+
if (new RegExp(`^var\\(--gem-${namespace}-[a-z0-9-]+\\)$`, "i").test(clean.trim())) return `--gem-${namespace}-${slugify(key)}: ${clean.trim()};`;
|
|
767
|
+
return `--gem-${namespace}-${slugify(key)}: calc(${clean} * var(${knob}, 1));`;
|
|
768
|
+
}
|
|
743
769
|
);
|
|
744
770
|
}
|
|
745
771
|
function toBorderCssVars(namespace, values, knob) {
|
|
@@ -1760,6 +1786,9 @@ function createPrimitiveCss() {
|
|
|
1760
1786
|
// and `.gem-button--soft` following button_style on the published storefront but NOT in the
|
|
1761
1787
|
// preview, which is the drift this rule exists to remove. `--primary` is named too so the
|
|
1762
1788
|
// modifier keeps winning where both could apply.
|
|
1789
|
+
// KNOWN (11/10/2026): these two also reach `.gem-button--link` (soft paints its tint behind a link-styled button,
|
|
1790
|
+
// outline recolours it). The gradient rule below leaves the link out; fixing these changes stores that did not ask,
|
|
1791
|
+
// so it waits for the next framework edition (editions.ts).
|
|
1763
1792
|
"[data-gem-button-style='outline'] .gem-button:not(.gem-button--secondary),",
|
|
1764
1793
|
"[data-gem-button-style='outline'] .gem-button--primary {",
|
|
1765
1794
|
" background: transparent;",
|
|
@@ -1771,6 +1800,28 @@ function createPrimitiveCss() {
|
|
|
1771
1800
|
" border-color: transparent;",
|
|
1772
1801
|
" color: var(--gem-color-text);",
|
|
1773
1802
|
"}",
|
|
1803
|
+
// `gradient` (0.13.0). GemFactory Global Style, 339 top-store pages: the main CTA is a gradient on 8.4% (10.6% of
|
|
1804
|
+
// landing pages), and 16 of the 21 measured are ONE hue going lighter → darker; 14 of the 21 drew their text under
|
|
1805
|
+
// 4.5:1. The far stop is the accent moved GEM_BUTTON_GRADIENT_SHIFT AWAY from the button text: toward black when the
|
|
1806
|
+
// accent is the darker of the two, toward white when it is the lighter (gemButtonGradientEnd is the same sum in JS;
|
|
1807
|
+
// contrast.ts has the two versions that failed), so the text only gains contrast across the button. It reads
|
|
1808
|
+
// --gem-color-accent / -accent-text, which a dark or accent band points at its own scheme, so a gradient button inside
|
|
1809
|
+
// a band follows the band. The text colour is set here because a bare `.gem-button` sets none. The link variant is
|
|
1810
|
+
// left out (it is text). Behind @supports, and in LONGHANDS: @supports only proves the syntax parses, and a value that
|
|
1811
|
+
// still fails at computed time (a var() that does not resolve) would take the whole `background` shorthand to
|
|
1812
|
+
// transparent under white text (review of #737, reproduced). As longhands only the image drops, over the accent.
|
|
1813
|
+
// Near black or white the two stops all but meet (#121212 → #0e0e0e): moving toward the text would break the AA
|
|
1814
|
+
// promise, so such a palette draws a flat button, and GemFactory's preset gate says so.
|
|
1815
|
+
"@supports (color: rgb(from color-mix(in srgb, red 50%, rgb(from blue calc(255 - r) g b)) clamp(0, (r - 127.5) * 1000, 255) g b)) {",
|
|
1816
|
+
" [data-gem-button-style='gradient'] .gem-button:not(.gem-button--secondary, .gem-button--link),",
|
|
1817
|
+
" [data-gem-button-style='gradient'] .gem-button--primary {",
|
|
1818
|
+
" background-color: var(--gem-color-accent);",
|
|
1819
|
+
` background-image: linear-gradient(135deg, var(--gem-color-accent), ${gemButtonGradientEndCss()});`,
|
|
1820
|
+
" background-origin: border-box;",
|
|
1821
|
+
" border-color: transparent;",
|
|
1822
|
+
" color: var(--gem-color-accent-text, var(--gem-color-surface));",
|
|
1823
|
+
" }",
|
|
1824
|
+
"}",
|
|
1774
1825
|
".gem-card {",
|
|
1775
1826
|
" border: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1776
1827
|
" border-radius: var(--gem-card-radius, var(--gem-radius-md));",
|
|
@@ -6382,9 +6433,9 @@ function renderHeroElementBlocks(section) {
|
|
|
6382
6433
|
const s = b?.settings ?? {};
|
|
6383
6434
|
if (b?.type === "countdown") {
|
|
6384
6435
|
const label = str(s.label) ? `<p class="gem-hero__countdown-label">${escapeHtml(str(s.label))}</p>` : "";
|
|
6385
|
-
const
|
|
6436
|
+
const target2 = escapeAttribute(str(s.end_date));
|
|
6386
6437
|
const unit = (n, l) => `<div class="gem-countdown__unit"><span class="gem-countdown__num" data-gem-cd-${n}>00</span><span class="gem-countdown__label">${l}</span></div>`;
|
|
6387
|
-
return `<div class="gem-hero__countdown">${label}<div class="gem-countdown" data-gem-countdown data-gem-countdown-target="${
|
|
6438
|
+
return `<div class="gem-hero__countdown">${label}<div class="gem-countdown" data-gem-countdown data-gem-countdown-target="${target2}" role="timer" aria-label="Offer countdown">${unit("days", "Days")}<div class="gem-countdown__sep">:</div>${unit("hours", "Hrs")}<div class="gem-countdown__sep">:</div>${unit("mins", "Min")}<div class="gem-countdown__sep">:</div>${unit("secs", "Sec")}</div></div>`;
|
|
6388
6439
|
}
|
|
6389
6440
|
if (b?.type === "trust_badges") {
|
|
6390
6441
|
const items = ["badge_1", "badge_2", "badge_3"].map((k) => str(s[k])).filter(Boolean).map((t) => `<li class="gem-hero__trust-item"><svg class="gem-hero__trust-check" viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>${escapeHtml(t)}</li>`).join("");
|
|
@@ -7248,6 +7299,8 @@ export {
|
|
|
7248
7299
|
GEM_AMBIENT_CSS,
|
|
7249
7300
|
GEM_BAND_SCHEMES,
|
|
7250
7301
|
GEM_BAND_SCHEME_FALLBACK_CSS,
|
|
7302
|
+
GEM_BUTTON_GRADIENT_PIVOT,
|
|
7303
|
+
GEM_BUTTON_GRADIENT_SHIFT,
|
|
7251
7304
|
GEM_BUTTON_TOKEN_KEYS,
|
|
7252
7305
|
GEM_BUTTON_TOKEN_SHAPES,
|
|
7253
7306
|
GEM_COUNTDOWN_ENDED_CSS,
|
|
@@ -7303,6 +7356,8 @@ export {
|
|
|
7303
7356
|
gemBandSchemeCssVars,
|
|
7304
7357
|
gemBandSchemeDeclarations,
|
|
7305
7358
|
gemButtonCaseCss,
|
|
7359
|
+
gemButtonGradientEnd,
|
|
7360
|
+
gemButtonGradientEndCss,
|
|
7306
7361
|
gemFormAttributes,
|
|
7307
7362
|
gemFormCssBlock,
|
|
7308
7363
|
gemFormCssVars,
|
package/dist/primitives.css
CHANGED
|
@@ -143,6 +143,16 @@ a {
|
|
|
143
143
|
border-color: transparent;
|
|
144
144
|
color: var(--gem-color-text);
|
|
145
145
|
}
|
|
146
|
+
@supports (color: rgb(from color-mix(in srgb, red 50%, rgb(from blue calc(255 - r) g b)) clamp(0, (r - 127.5) * 1000, 255) g b)) {
|
|
147
|
+
[data-gem-button-style='gradient'] .gem-button:not(.gem-button--secondary, .gem-button--link),
|
|
148
|
+
[data-gem-button-style='gradient'] .gem-button--primary {
|
|
149
|
+
background-color: var(--gem-color-accent);
|
|
150
|
+
background-image: linear-gradient(135deg, var(--gem-color-accent), color-mix(in srgb, var(--gem-color-accent) 75%, rgb(from color-mix(in srgb, var(--gem-color-accent) 50%, rgb(from var(--gem-color-accent-text, var(--gem-color-surface)) calc(255 - r) calc(255 - g) calc(255 - b))) clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - 127.5) * 1000, 255) clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - 127.5) * 1000, 255) clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - 127.5) * 1000, 255) / 1)));
|
|
151
|
+
background-origin: border-box;
|
|
152
|
+
border-color: transparent;
|
|
153
|
+
color: var(--gem-color-accent-text, var(--gem-color-surface));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
146
156
|
.gem-card {
|
|
147
157
|
border: var(--gem-border-subtle, 1px solid var(--gem-color-border));
|
|
148
158
|
border-radius: var(--gem-card-radius, var(--gem-radius-md));
|
package/dist/section-gate.js
CHANGED
|
@@ -139,6 +139,13 @@ function aaLabelColor(preferred, fill, fallbacks = []) {
|
|
|
139
139
|
if (pass) return pass;
|
|
140
140
|
return candidates.reduce((a, b) => hexContrastRatio(b, ground) > hexContrastRatio(a, ground) ? b : a, preferred);
|
|
141
141
|
}
|
|
142
|
+
var GEM_BUTTON_GRADIENT_SHIFT = 0.25;
|
|
143
|
+
var GEM_BUTTON_GRADIENT_PIVOT = 127.5;
|
|
144
|
+
function gemButtonGradientEndCss(accent = "var(--gem-color-accent)", accentText = "var(--gem-color-accent-text, var(--gem-color-surface))") {
|
|
145
|
+
const t = `clamp(0, (0.2126 * r + 0.7152 * g + 0.0722 * b - ${GEM_BUTTON_GRADIENT_PIVOT}) * 1000, 255)`;
|
|
146
|
+
const probe = `color-mix(in srgb, ${accent} 50%, rgb(from ${accentText} calc(255 - r) calc(255 - g) calc(255 - b)))`;
|
|
147
|
+
return `color-mix(in srgb, ${accent} ${100 - GEM_BUTTON_GRADIENT_SHIFT * 100}%, rgb(from ${probe} ${t} ${t} ${t} / 1))`;
|
|
148
|
+
}
|
|
142
149
|
|
|
143
150
|
// ../../packages/gem-framework/src/tokens.ts
|
|
144
151
|
var GEM_FOCUS_RING_CSS_VAR = "--gem-focus-ring: 2px solid var(--gem-color-accent);";
|
|
@@ -205,7 +212,7 @@ function readButtonTokens(tokens) {
|
|
|
205
212
|
const key = byNorm.get(buttonKeyNorm(rawKey));
|
|
206
213
|
if (!key) continue;
|
|
207
214
|
copy ??= { ...values };
|
|
208
|
-
delete copy[rawKey];
|
|
215
|
+
if (rawKey !== key) delete copy[rawKey];
|
|
209
216
|
const v = buttonTokenValue(group, key, raw);
|
|
210
217
|
if (v === null) out.invalid.push({ path: `${group}.${rawKey}`, value: raw });
|
|
211
218
|
else {
|
|
@@ -213,6 +220,9 @@ function readButtonTokens(tokens) {
|
|
|
213
220
|
out.values[key] = v;
|
|
214
221
|
}
|
|
215
222
|
}
|
|
223
|
+
if (copy) {
|
|
224
|
+
for (const key of keys) if (!(key in out.values)) delete copy[key];
|
|
225
|
+
}
|
|
216
226
|
if (copy) out.groups[group] = copy;
|
|
217
227
|
}
|
|
218
228
|
return out;
|
|
@@ -575,7 +585,11 @@ function toScaledCssVars(namespace, values, knob) {
|
|
|
575
585
|
// bọc nó vào GIỮA giá trị — `calc(16px !important * var(...))` — nên custom property lưu
|
|
576
586
|
// nguyên xi, rồi mọi `padding: var(--gem-space-md)` thành invalid ở computed-value và SẬP VỀ 0
|
|
577
587
|
// trên toàn bộ .gem-* section.
|
|
578
|
-
([key, value]) =>
|
|
588
|
+
([key, value]) => {
|
|
589
|
+
const clean = stripImportant(value);
|
|
590
|
+
if (new RegExp(`^var\\(--gem-${namespace}-[a-z0-9-]+\\)$`, "i").test(clean.trim())) return `--gem-${namespace}-${slugify(key)}: ${clean.trim()};`;
|
|
591
|
+
return `--gem-${namespace}-${slugify(key)}: calc(${clean} * var(${knob}, 1));`;
|
|
592
|
+
}
|
|
579
593
|
);
|
|
580
594
|
}
|
|
581
595
|
function toBorderCssVars(namespace, values, knob) {
|
|
@@ -1431,6 +1445,9 @@ function createPrimitiveCss() {
|
|
|
1431
1445
|
// and `.gem-button--soft` following button_style on the published storefront but NOT in the
|
|
1432
1446
|
// preview, which is the drift this rule exists to remove. `--primary` is named too so the
|
|
1433
1447
|
// modifier keeps winning where both could apply.
|
|
1448
|
+
// KNOWN (11/10/2026): these two also reach `.gem-button--link` (soft paints its tint behind a link-styled button,
|
|
1449
|
+
// outline recolours it). The gradient rule below leaves the link out; fixing these changes stores that did not ask,
|
|
1450
|
+
// so it waits for the next framework edition (editions.ts).
|
|
1434
1451
|
"[data-gem-button-style='outline'] .gem-button:not(.gem-button--secondary),",
|
|
1435
1452
|
"[data-gem-button-style='outline'] .gem-button--primary {",
|
|
1436
1453
|
" background: transparent;",
|
|
@@ -1442,6 +1459,28 @@ function createPrimitiveCss() {
|
|
|
1442
1459
|
" border-color: transparent;",
|
|
1443
1460
|
" color: var(--gem-color-text);",
|
|
1444
1461
|
"}",
|
|
1462
|
+
// `gradient` (0.13.0). GemFactory Global Style, 339 top-store pages: the main CTA is a gradient on 8.4% (10.6% of
|
|
1463
|
+
// landing pages), and 16 of the 21 measured are ONE hue going lighter → darker; 14 of the 21 drew their text under
|
|
1464
|
+
// 4.5:1. The far stop is the accent moved GEM_BUTTON_GRADIENT_SHIFT AWAY from the button text: toward black when the
|
|
1465
|
+
// accent is the darker of the two, toward white when it is the lighter (gemButtonGradientEnd is the same sum in JS;
|
|
1466
|
+
// contrast.ts has the two versions that failed), so the text only gains contrast across the button. It reads
|
|
1467
|
+
// --gem-color-accent / -accent-text, which a dark or accent band points at its own scheme, so a gradient button inside
|
|
1468
|
+
// a band follows the band. The text colour is set here because a bare `.gem-button` sets none. The link variant is
|
|
1469
|
+
// left out (it is text). Behind @supports, and in LONGHANDS: @supports only proves the syntax parses, and a value that
|
|
1470
|
+
// still fails at computed time (a var() that does not resolve) would take the whole `background` shorthand to
|
|
1471
|
+
// transparent under white text (review of #737, reproduced). As longhands only the image drops, over the accent.
|
|
1472
|
+
// Near black or white the two stops all but meet (#121212 → #0e0e0e): moving toward the text would break the AA
|
|
1473
|
+
// promise, so such a palette draws a flat button, and GemFactory's preset gate says so.
|
|
1474
|
+
"@supports (color: rgb(from color-mix(in srgb, red 50%, rgb(from blue calc(255 - r) g b)) clamp(0, (r - 127.5) * 1000, 255) g b)) {",
|
|
1475
|
+
" [data-gem-button-style='gradient'] .gem-button:not(.gem-button--secondary, .gem-button--link),",
|
|
1476
|
+
" [data-gem-button-style='gradient'] .gem-button--primary {",
|
|
1477
|
+
" background-color: var(--gem-color-accent);",
|
|
1478
|
+
` background-image: linear-gradient(135deg, var(--gem-color-accent), ${gemButtonGradientEndCss()});`,
|
|
1479
|
+
" background-origin: border-box;",
|
|
1480
|
+
" border-color: transparent;",
|
|
1481
|
+
" color: var(--gem-color-accent-text, var(--gem-color-surface));",
|
|
1482
|
+
" }",
|
|
1483
|
+
"}",
|
|
1445
1484
|
".gem-card {",
|
|
1446
1485
|
" border: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1447
1486
|
" border-radius: var(--gem-card-radius, var(--gem-radius-md));",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/gem-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|