@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 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.12.0",
4
- "gemtheme_sha": "19bae499182a6b3479f331ae4c4647458d3625c5",
3
+ "version": "0.13.0",
4
+ "gemtheme_sha": "3e6e73a7466bf54609b5e81abfc3c4d4a06b42d6",
5
5
  "gemtheme_dirty": false,
6
- "framework_version": "0.12.0"
6
+ "framework_version": "0.13.0"
7
7
  }
@@ -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.12.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 target = gemPhoneTokenTarget(group, key);
465
- if (target == null) continue;
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][target] = "0px";
470
- else if (kind === "length" && !ownGroup.test(v)) out[group][target] = v;
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]) => `--gem-${namespace}-${slugify(key)}: calc(${stripImportant(value)} * var(${knob}, 1));`
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 target = escapeAttribute(str(s.end_date));
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="${target}" 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>`;
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,
@@ -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));
@@ -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]) => `--gem-${namespace}-${slugify(key)}: calc(${stripImportant(value)} * var(${knob}, 1));`
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.12.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",