@gemboss/gem-framework 0.4.0 → 0.5.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/dist/SOURCE.json +2 -2
- package/dist/gem-framework.css +74 -5
- package/dist/gem-runtime.js +28 -6
- package/dist/index.js +207 -20
- package/dist/section-gate.js +108 -5
- package/package.json +1 -1
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.5.1",
|
|
4
|
+
"gemtheme_sha": "b0ca8d5022be58f1deb0102c302de2b2ee6ef12c",
|
|
5
5
|
"gemtheme_dirty": false,
|
|
6
6
|
"framework_version": "0.1.0"
|
|
7
7
|
}
|
package/dist/gem-framework.css
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
--gem-space-xl: calc(32px * var(--gem-style-space, 1));
|
|
20
20
|
--gem-space-2xl: calc(48px * var(--gem-style-space, 1));
|
|
21
21
|
--gem-space-3xl: calc(64px * var(--gem-style-space, 1));
|
|
22
|
+
--gem-space-2xs: calc(var(--gem-space-md) * 0.125);
|
|
23
|
+
--gem-space-ms: calc(var(--gem-space-md) * 0.75);
|
|
24
|
+
--gem-space-4xl: calc(var(--gem-space-md) * 7);
|
|
22
25
|
--gem-radius-sm: calc(4px * var(--gem-style-radius, 1));
|
|
23
26
|
--gem-radius-md: calc(8px * var(--gem-style-radius, 1));
|
|
24
27
|
--gem-radius-lg: calc(16px * var(--gem-style-radius, 1));
|
|
@@ -59,6 +62,7 @@
|
|
|
59
62
|
--gem-h1: calc(var(--gem-h2) * var(--gem-type-scale));
|
|
60
63
|
--gem-focus-ring: 2px solid var(--gem-color-accent);
|
|
61
64
|
--gem-tap-min: 44px;
|
|
65
|
+
--gem-focus-offset: 3px;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
*, *::before, *::after {
|
|
@@ -93,9 +97,15 @@ button, [type='button'], [type='submit'] {
|
|
|
93
97
|
a {
|
|
94
98
|
color: inherit;
|
|
95
99
|
}
|
|
100
|
+
:where(.gem-section, [data-gem-section-id]) {
|
|
101
|
+
--gem-focus-ring: 2px solid var(--gem-color-accent);
|
|
102
|
+
}
|
|
96
103
|
:focus-visible {
|
|
97
104
|
outline: var(--gem-focus-ring, 2px solid var(--gem-color-accent));
|
|
98
|
-
outline-offset: 3px;
|
|
105
|
+
outline-offset: var(--gem-focus-offset, 3px);
|
|
106
|
+
}
|
|
107
|
+
:where([data-gem-section-background='accent'], [data-gem-section-background='dark']) :focus-visible:focus-visible {
|
|
108
|
+
box-shadow: 0 0 0 calc(var(--gem-focus-offset, 3px) + 4px) var(--gem-band-bg);
|
|
99
109
|
}
|
|
100
110
|
.gem-skip-to-content-link:focus {
|
|
101
111
|
position: fixed;
|
|
@@ -267,6 +277,46 @@ a {
|
|
|
267
277
|
color: var(--gem-color-text);
|
|
268
278
|
padding: 0 var(--gem-space-md);
|
|
269
279
|
}
|
|
280
|
+
.gem-field-join {
|
|
281
|
+
display: flex;
|
|
282
|
+
align-items: stretch;
|
|
283
|
+
min-width: 0;
|
|
284
|
+
}
|
|
285
|
+
.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden) {
|
|
286
|
+
min-height: var(--gem-component-input-height, 44px);
|
|
287
|
+
margin: 0;
|
|
288
|
+
}
|
|
289
|
+
.gem-field-join > :is(.gem-input, .gem-select, .gem-form-field) {
|
|
290
|
+
flex: 1 1 10rem;
|
|
291
|
+
width: auto;
|
|
292
|
+
min-width: min(10rem, 50%);
|
|
293
|
+
}
|
|
294
|
+
.gem-field-join > .gem-form-field > :is(.gem-input, .gem-select) {
|
|
295
|
+
width: 100%;
|
|
296
|
+
height: 100%;
|
|
297
|
+
min-height: var(--gem-component-input-height, 44px);
|
|
298
|
+
}
|
|
299
|
+
.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(.gem-input, .gem-select, .gem-form-field) {
|
|
300
|
+
flex: 0 1 auto;
|
|
301
|
+
min-width: var(--gem-component-input-height, 44px);
|
|
302
|
+
white-space: normal;
|
|
303
|
+
overflow-wrap: anywhere;
|
|
304
|
+
}
|
|
305
|
+
.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(:nth-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))), .gem-field-join > .gem-form-field:not(:nth-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))) > :is(.gem-input, .gem-select) {
|
|
306
|
+
border-start-start-radius: 0;
|
|
307
|
+
border-end-start-radius: 0;
|
|
308
|
+
}
|
|
309
|
+
.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(:nth-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))) {
|
|
310
|
+
margin-inline-start: calc(-1 * var(--gem-input-border-width, 1px));
|
|
311
|
+
}
|
|
312
|
+
.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(:nth-last-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))), .gem-field-join > .gem-form-field:not(:nth-last-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))) > :is(.gem-input, .gem-select) {
|
|
313
|
+
border-start-end-radius: 0;
|
|
314
|
+
border-end-end-radius: 0;
|
|
315
|
+
}
|
|
316
|
+
.gem-field-join > :focus-within {
|
|
317
|
+
position: relative;
|
|
318
|
+
z-index: 1;
|
|
319
|
+
}
|
|
270
320
|
.gem-textarea {
|
|
271
321
|
min-height: calc(var(--gem-component-input-height, 44px) * 2);
|
|
272
322
|
padding-block: var(--gem-space-sm);
|
|
@@ -307,10 +357,17 @@ a {
|
|
|
307
357
|
display: inline-flex;
|
|
308
358
|
align-items: center;
|
|
309
359
|
justify-content: center;
|
|
310
|
-
inline-size: var(--gem-space-lg);
|
|
311
|
-
block-size: var(--gem-space-lg);
|
|
360
|
+
inline-size: var(--gem-icon-size, var(--gem-space-lg));
|
|
361
|
+
block-size: var(--gem-icon-size, var(--gem-space-lg));
|
|
312
362
|
color: var(--gem-color-accent);
|
|
313
363
|
}
|
|
364
|
+
.gem-icon--sm { --gem-icon-size: 16px; }
|
|
365
|
+
.gem-icon--md { --gem-icon-size: 20px; }
|
|
366
|
+
.gem-icon--lg { --gem-icon-size: 24px; }
|
|
367
|
+
.gem-icon--current { color: currentColor; }
|
|
368
|
+
.gem-icon--chevron { transition: rotate var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease); }
|
|
369
|
+
:is(button, summary, [role='button'])[aria-expanded='true'] > .gem-icon--chevron, details[open] > summary > .gem-icon--chevron { rotate: 180deg; }
|
|
370
|
+
@media (prefers-reduced-motion: reduce) { .gem-icon--chevron { transition: none; } }
|
|
314
371
|
.gem-icon-list {
|
|
315
372
|
display: grid;
|
|
316
373
|
gap: var(--gem-space-sm);
|
|
@@ -1021,6 +1078,10 @@ a {
|
|
|
1021
1078
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-card='outline'] :is(.gem-section__inner, [class*='__inner']) { border: 1px solid var(--gem-color-border); }
|
|
1022
1079
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-card='shadow'] :is(.gem-section__inner, [class*='__inner']) { background: var(--gem-color-surface); box-shadow: var(--gem-shadow-md); }
|
|
1023
1080
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-card='filled'] :is(.gem-section__inner, [class*='__inner']) { background: color-mix(in srgb, currentColor 6%, transparent); }
|
|
1081
|
+
:root:not(:has([data-gem-canvas])) [data-gem-section-id][data-gem-countdown-ended='true'] { display: none; }
|
|
1082
|
+
:root:has([data-gem-canvas]) [data-gem-section-id][data-gem-countdown-ended='true'] { opacity: .55; }
|
|
1083
|
+
.gem-countdown-ended-note { display: none; }
|
|
1084
|
+
:root:has([data-gem-canvas]) [data-gem-countdown-ended='true'] .gem-countdown-ended-note { display: block; margin: 0; padding: .25rem 0; font-size: .75rem; font-weight: 700; text-align: center; }
|
|
1024
1085
|
.gem-promo-banner, .gem-promo-tile {
|
|
1025
1086
|
border: var(--gem-border-subtle, 1px solid var(--gem-color-border));
|
|
1026
1087
|
border-radius: var(--gem-radius-md);
|
|
@@ -1124,6 +1185,11 @@ a {
|
|
|
1124
1185
|
inset: 0;
|
|
1125
1186
|
background: rgba(0, 0, 0, 0.36);
|
|
1126
1187
|
}
|
|
1188
|
+
@keyframes gem-cart-drawer-panel-in { from { translate: calc(var(--gem-drawer-offset, 24px) * var(--gem-drawer-dir, 1)) 0; opacity: 0; } }
|
|
1189
|
+
@keyframes gem-cart-drawer-overlay-in { from { opacity: 0; } }
|
|
1190
|
+
.gem-cart-drawer__panel:dir(rtl) { --gem-drawer-dir: -1; }
|
|
1191
|
+
.gem-cart-drawer[data-gem-drawer-motion='slide'] .gem-cart-drawer__overlay { animation: gem-cart-drawer-overlay-in var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease); }
|
|
1192
|
+
.gem-cart-drawer[data-gem-drawer-motion='slide'] .gem-cart-drawer__panel { animation: gem-cart-drawer-panel-in var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease); }
|
|
1127
1193
|
.gem-cart-drawer__panel {
|
|
1128
1194
|
position: fixed;
|
|
1129
1195
|
inset-block: 0;
|
|
@@ -1136,6 +1202,7 @@ a {
|
|
|
1136
1202
|
box-shadow: var(--gem-shadow-md, none);
|
|
1137
1203
|
padding: var(--gem-space-lg);
|
|
1138
1204
|
}
|
|
1205
|
+
@media (prefers-reduced-motion: reduce) { .gem-cart-drawer[data-gem-drawer-motion] :is(.gem-cart-drawer__panel, .gem-cart-drawer__overlay) { animation: none; } }
|
|
1139
1206
|
.gem-cart-drawer__header, .gem-cart-drawer__footer {
|
|
1140
1207
|
display: flex;
|
|
1141
1208
|
flex-wrap: wrap;
|
|
@@ -1989,8 +2056,8 @@ sticky-header.header-wrapper {
|
|
|
1989
2056
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-grid-columns='4'] .gem-row:is(.gem-section__grid, [class*='__grid']):not(.gem-row .gem-row) { --gem-row-cols: 4; --gem-row-cols-md: 4; --gem-row-tracks: initial; --gem-row-tracks-md: initial; }
|
|
1990
2057
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='default'] { --gem-section-bg: var(--gem-color-background); background: transparent; }
|
|
1991
2058
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='surface'] { --gem-section-bg: var(--gem-color-surface); background: var(--gem-section-bg); padding-block: var(--gem-space-xl); }
|
|
1992
|
-
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='accent'] { --gem-section-bg: var(--gem-color-accent); background: var(--gem-section-bg); color: var(--gem-color-accent-text); padding-block: var(--gem-space-xl); }
|
|
1993
|
-
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='dark'] { --gem-section-bg: var(--gem-color-text); background: var(--gem-section-bg); color: var(--gem-color-background); padding-block: var(--gem-space-xl); }
|
|
2059
|
+
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='accent'] { --gem-section-bg: var(--gem-color-accent); background: var(--gem-section-bg); color: var(--gem-color-accent-text); --gem-band-bg: var(--gem-color-accent); --gem-focus-ring: 2px solid var(--gem-color-accent-text); padding-block: var(--gem-space-xl); }
|
|
2060
|
+
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='dark'] { --gem-section-bg: var(--gem-color-text); background: var(--gem-section-bg); color: var(--gem-color-background); --gem-band-bg: var(--gem-color-text); --gem-focus-ring: 2px solid var(--gem-color-background); padding-block: var(--gem-space-xl); }
|
|
1994
2061
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='light'] { --gem-section-bg: var(--gem-color-background); background: var(--gem-section-bg); color: var(--gem-color-text); padding-block: var(--gem-space-xl); }
|
|
1995
2062
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='accent'] :is(.gem-section__heading, .gem-section__body, .gem-section__eyebrow), :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='dark'] :is(.gem-section__heading, .gem-section__body, .gem-section__eyebrow) { color: inherit; }
|
|
1996
2063
|
:is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-price-color='accent'] :is(.gem-price, .gem-price__regular, [class*='__price']) { color: var(--gem-color-accent); }
|
|
@@ -2151,6 +2218,7 @@ sticky-header.header-wrapper {
|
|
|
2151
2218
|
}
|
|
2152
2219
|
:where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; letter-spacing: 0.12em; }
|
|
2153
2220
|
: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; }
|
|
2221
|
+
: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); }
|
|
2154
2222
|
@media (max-width: 760px) {
|
|
2155
2223
|
:where(.gem-section[data-gem-heading='section']) .gem-section__heading { font-size: min(var(--gem-h2), clamp(1.75rem, 9vw, 2.75rem)); }
|
|
2156
2224
|
}
|
|
@@ -2369,6 +2437,7 @@ main > .gem-section:first-child { padding-top: 0; }
|
|
|
2369
2437
|
.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; }
|
|
2370
2438
|
:where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; letter-spacing: 0.12em; }
|
|
2371
2439
|
: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; }
|
|
2440
|
+
: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); }
|
|
2372
2441
|
@media (max-width: 760px) {
|
|
2373
2442
|
:root { --gem-layout-gutter: 24px; --gem-layout-section: 36px; }
|
|
2374
2443
|
.gem-shell-header, .gem-shell-header__inner, .header { min-height: 64px; }
|
package/dist/gem-runtime.js
CHANGED
|
@@ -645,32 +645,54 @@
|
|
|
645
645
|
const timers = document.querySelectorAll('[data-gem-countdown], .gem-countdown');
|
|
646
646
|
let bound = 0;
|
|
647
647
|
const pad = (value) => String(Math.max(0, value)).padStart(2, '0');
|
|
648
|
+
const COUNTDOWN_ISO = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/i;
|
|
649
|
+
const countdownEnd = (value) => {
|
|
650
|
+
if (typeof value !== 'string') return NaN;
|
|
651
|
+
const raw = value.trim();
|
|
652
|
+
const m = COUNTDOWN_ISO.exec(raw);
|
|
653
|
+
if (!m) return NaN;
|
|
654
|
+
const y = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
|
|
655
|
+
const day = new Date(Date.UTC(y, mo - 1, d));
|
|
656
|
+
if (day.getUTCFullYear() !== y || day.getUTCMonth() !== mo - 1 || day.getUTCDate() !== d) return NaN;
|
|
657
|
+
if (m[4] === undefined) return Date.parse(m[1] + '-' + m[2] + '-' + m[3] + 'T23:59:59Z');
|
|
658
|
+
const iso = m[1] + '-' + m[2] + '-' + m[3] + 'T' + raw.slice(11);
|
|
659
|
+
return Date.parse(m[7] ? iso : iso + 'Z');
|
|
660
|
+
};
|
|
648
661
|
const write = (timer, selector, value) => {
|
|
649
662
|
const targetNode = timer.querySelector && timer.querySelector(selector);
|
|
650
663
|
if (targetNode) targetNode.textContent = pad(value);
|
|
651
664
|
};
|
|
652
665
|
timers.forEach((timer) => {
|
|
653
666
|
if (!mark(timer, 'countdown')) return;
|
|
654
|
-
const
|
|
655
|
-
const
|
|
667
|
+
const targetTime = countdownEnd(timer.getAttribute && timer.getAttribute('data-gem-countdown-target'));
|
|
668
|
+
const section = timer.closest ? timer.closest('[data-gem-section-id]') : null;
|
|
669
|
+
const band = section && section.hasAttribute && section.hasAttribute('data-gem-countdown-end') ? section : null;
|
|
670
|
+
let handle = null;
|
|
656
671
|
const update = () => {
|
|
657
672
|
if (!Number.isFinite(targetTime)) {
|
|
658
673
|
timer.dataset.gemCountdownState = 'invalid';
|
|
659
674
|
return;
|
|
660
675
|
}
|
|
661
|
-
const
|
|
676
|
+
const ms = targetTime - Date.now();
|
|
677
|
+
const remainingSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
662
678
|
const days = Math.floor(remainingSeconds / 86400);
|
|
663
679
|
const hours = Math.floor((remainingSeconds % 86400) / 3600);
|
|
664
680
|
const minutes = Math.floor((remainingSeconds % 3600) / 60);
|
|
665
|
-
timer.dataset.gemCountdownState =
|
|
681
|
+
timer.dataset.gemCountdownState = ms > 0 ? 'running' : 'complete';
|
|
666
682
|
timer.dataset.gemCountdownRemainingSeconds = String(remainingSeconds);
|
|
667
683
|
write(timer, '[data-gem-countdown-days]', days);
|
|
668
684
|
write(timer, '[data-gem-countdown-hours]', hours);
|
|
669
685
|
write(timer, '[data-gem-countdown-minutes]', minutes);
|
|
670
|
-
|
|
686
|
+
if (ms <= 0) {
|
|
687
|
+
if (band) band.setAttribute('data-gem-countdown-ended', 'true');
|
|
688
|
+
if (handle !== null && target.clearInterval) { target.clearInterval(handle); handle = null; }
|
|
689
|
+
}
|
|
690
|
+
api.emit(ms > 0 ? 'gem:countdown:start' : 'gem:countdown:complete', { source: 'gem-runtime', id: timer.id || undefined, remainingSeconds });
|
|
671
691
|
};
|
|
672
692
|
update();
|
|
673
|
-
if (target.setInterval && Number.isFinite(targetTime)) target.setInterval(update, 60000);
|
|
693
|
+
if (target.setInterval && Number.isFinite(targetTime) && targetTime - Date.now() > 0) handle = target.setInterval(update, 60000);
|
|
694
|
+
const left = targetTime - Date.now();
|
|
695
|
+
if (target.setTimeout && Number.isFinite(left) && left > 0 && left < 2147483647) target.setTimeout(update, left);
|
|
674
696
|
bound += 1;
|
|
675
697
|
});
|
|
676
698
|
api.emit('gem:countdown:ready', { source: 'gem-runtime', count: bound });
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,27 @@ function gemPlanShapeKnobs(plan) {
|
|
|
82
82
|
var gemFrameworkVersion = "0.1.0";
|
|
83
83
|
var GEM_FOCUS_RING_CSS_VAR = "--gem-focus-ring: 2px solid var(--gem-color-accent);";
|
|
84
84
|
var GEM_TAP_MIN_CSS_VAR = "--gem-tap-min: 44px;";
|
|
85
|
+
var GEM_FOCUS_OFFSET_CSS_VAR = "--gem-focus-offset: 3px;";
|
|
86
|
+
var GEM_DERIVED_SPACE_STEPS = { "2xs": 0.125, ms: 0.75, "4xl": 7 };
|
|
87
|
+
var GEM_DERIVED_SPACE_CSS_VARIABLES = Object.keys(GEM_DERIVED_SPACE_STEPS).map((key) => `--gem-space-${slugify(key)}`);
|
|
88
|
+
function gemDerivedSpaceCssVars(spacing) {
|
|
89
|
+
const named = new Set(Object.entries(spacing ?? {}).filter(([, v]) => String(v ?? "").trim()).map(([k]) => slugify(k)));
|
|
90
|
+
const md = gemSpaceMdKind(spacing?.md);
|
|
91
|
+
return Object.entries(GEM_DERIVED_SPACE_STEPS).filter(([key]) => !named.has(slugify(key))).map(([key, factor]) => `--gem-space-${slugify(key)}: ${derivedSpaceValue(md, factor)};`);
|
|
92
|
+
}
|
|
93
|
+
function gemSpaceMdKind(raw) {
|
|
94
|
+
const md = stripImportant(String(raw ?? "")).trim();
|
|
95
|
+
if (md === "") return "absent";
|
|
96
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)$/.test(md)) return Number(md) === 0 ? "zero" : "invalid";
|
|
97
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?[a-z%]+$/i.test(md)) return "length";
|
|
98
|
+
if (/^(var|calc|clamp|min|max|env)\(.*\)$/i.test(md)) return "length";
|
|
99
|
+
return "invalid";
|
|
100
|
+
}
|
|
101
|
+
function derivedSpaceValue(md, factor) {
|
|
102
|
+
if (md === "zero") return "0";
|
|
103
|
+
if (md === "invalid") return `calc(${parseFloat(defaultGemTokens.spacing.md) * factor}px * var(--gem-style-space, 1))`;
|
|
104
|
+
return `calc(var(--gem-space-md) * ${factor})`;
|
|
105
|
+
}
|
|
85
106
|
var GEM_ROW_CSS_VARIABLES = [
|
|
86
107
|
"--gem-row-cols",
|
|
87
108
|
"--gem-row-cols-md",
|
|
@@ -214,11 +235,12 @@ function validateGemTokens(tokens) {
|
|
|
214
235
|
function createGemTokenCss(tokens, selector = ":root") {
|
|
215
236
|
const declarations = [
|
|
216
237
|
...toCssVars("color", tokens.color),
|
|
217
|
-
...toCssVars("font", tokens.typography),
|
|
238
|
+
...toCssVars("font", Object.fromEntries(Object.entries(tokens.typography ?? {}).filter(([k]) => !isRampKey(k)))),
|
|
218
239
|
// Space ramp is scaled by the global density knob --gem-style-space (default 1),
|
|
219
240
|
// so a style pack can make the WHOLE storefront airier or denser without touching
|
|
220
241
|
// any section — every section already consumes --gem-space-*. Knob unset → identical.
|
|
221
242
|
...toScaledCssVars("space", tokens.spacing, "--gem-style-space"),
|
|
243
|
+
...gemDerivedSpaceCssVars(tokens.spacing),
|
|
222
244
|
// Radius ramp scaled by --gem-style-radius (default 1): a 'sharp' style sets it to
|
|
223
245
|
// 0 (square everything), 'pill' rounds further — across every section, no edits.
|
|
224
246
|
...toScaledCssVars("radius", tokens.radius, "--gem-style-radius"),
|
|
@@ -239,24 +261,39 @@ function createGemTokenCss(tokens, selector = ":root") {
|
|
|
239
261
|
// live framework defined them: a rebuild that followed the contract styled its headings with
|
|
240
262
|
// variables that resolved to nothing. One modular scale, retuned by overriding --gem-type-base
|
|
241
263
|
// or --gem-type-scale (see gemTypeRampCssVars).
|
|
242
|
-
...gemTypeRampCssVars(),
|
|
264
|
+
...gemTypeRampCssVars(tokens.typography),
|
|
243
265
|
// Focus ring (gemfactory request #3, 28/09/2026): 11 sections wrote `outline: 2px solid
|
|
244
266
|
// var(--gem-color-accent)` by hand. Not a token GROUP (a group would name it --gem-border-focus-ring
|
|
245
267
|
// and a required key would fail every stored plan/preset without it), so it is emitted here like the
|
|
246
268
|
// type ramp and registered in validation.ts. Follows the accent, so presets retint it for free.
|
|
247
269
|
GEM_FOCUS_RING_CSS_VAR,
|
|
248
|
-
GEM_TAP_MIN_CSS_VAR
|
|
270
|
+
GEM_TAP_MIN_CSS_VAR,
|
|
271
|
+
GEM_FOCUS_OFFSET_CSS_VAR
|
|
249
272
|
];
|
|
273
|
+
const baseMobile = typeRampValue(tokens.typography, "baseMobile");
|
|
250
274
|
return [
|
|
251
275
|
`${selector} {`,
|
|
252
276
|
...declarations.map((line) => ` ${line}`),
|
|
253
|
-
"}"
|
|
277
|
+
"}",
|
|
278
|
+
...baseMobile ? [`@media (max-width: ${GEM_TYPE_MOBILE_MAX}) {`, ` ${selector} {`, ` --gem-type-base: ${baseMobile};`, " }", "}"] : []
|
|
254
279
|
].join("\n");
|
|
255
280
|
}
|
|
256
|
-
|
|
281
|
+
var GEM_TYPE_RAMP_KEYS = ["base", "scale", "baseMobile"];
|
|
282
|
+
var rampKey = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
283
|
+
var isRampKey = (k) => GEM_TYPE_RAMP_KEYS.some((r) => rampKey(r) === rampKey(k));
|
|
284
|
+
var LENGTH = /^\d*\.?\d+(px|rem|em)$/;
|
|
285
|
+
var NUMBER = /^\d*\.?\d+$/;
|
|
286
|
+
var typeRampValue = (typography, key) => {
|
|
287
|
+
const hit = Object.entries(typography ?? {}).find(([k]) => rampKey(k) === rampKey(key));
|
|
288
|
+
const v = hit == null || hit[1] == null ? "" : stripImportant(hit[1]).trim();
|
|
289
|
+
if (!v) return null;
|
|
290
|
+
return (key === "scale" ? NUMBER.test(v) && Number(v) > 0 : LENGTH.test(v)) ? v : null;
|
|
291
|
+
};
|
|
292
|
+
var GEM_TYPE_MOBILE_MAX = "749px";
|
|
293
|
+
function gemTypeRampCssVars(typography) {
|
|
257
294
|
return [
|
|
258
|
-
|
|
259
|
-
|
|
295
|
+
`--gem-type-base: ${typeRampValue(typography, "base") ?? "1rem"};`,
|
|
296
|
+
`--gem-type-scale: ${typeRampValue(typography, "scale") ?? "1.22"};`,
|
|
260
297
|
"--gem-text-sm: calc(var(--gem-type-base) / var(--gem-type-scale));",
|
|
261
298
|
"--gem-body: var(--gem-type-base);",
|
|
262
299
|
"--gem-h6: calc(var(--gem-type-base) * var(--gem-type-scale));",
|
|
@@ -333,7 +370,9 @@ var gemFrameworkPrimitiveGroups = [
|
|
|
333
370
|
},
|
|
334
371
|
{
|
|
335
372
|
group: "surfaces",
|
|
336
|
-
|
|
373
|
+
// `gem-countdown-ended-note`: the editor-canvas line every countdown band carries, styled once in
|
|
374
|
+
// primitives.ts GEM_COUNTDOWN_ENDED_CSS (hidden on the store, shown on an ended band in the canvas).
|
|
375
|
+
classes: ["gem-card", "gem-media", "gem-badge", "gem-accordion", "gem-divider", "gem-countdown-ended-note"]
|
|
337
376
|
},
|
|
338
377
|
{
|
|
339
378
|
group: "forms",
|
|
@@ -344,7 +383,9 @@ var gemFrameworkPrimitiveGroups = [
|
|
|
344
383
|
"gem-select",
|
|
345
384
|
"gem-textarea",
|
|
346
385
|
"gem-submit",
|
|
347
|
-
"gem-newsletter-form"
|
|
386
|
+
"gem-newsletter-form",
|
|
387
|
+
// Input + button as one control (primitives.ts, gemfactory request #8).
|
|
388
|
+
"gem-field-join"
|
|
348
389
|
]
|
|
349
390
|
},
|
|
350
391
|
{
|
|
@@ -356,6 +397,12 @@ var gemFrameworkPrimitiveGroups = [
|
|
|
356
397
|
// storefront alongside gem-section__ctas.
|
|
357
398
|
"gem-prose",
|
|
358
399
|
"gem-icon",
|
|
400
|
+
// Size / colour / rotate hooks (gemfactory request #11); the bare class renders as before.
|
|
401
|
+
"gem-icon--sm",
|
|
402
|
+
"gem-icon--md",
|
|
403
|
+
"gem-icon--lg",
|
|
404
|
+
"gem-icon--current",
|
|
405
|
+
"gem-icon--chevron",
|
|
359
406
|
"gem-icon-list",
|
|
360
407
|
"gem-icon-list__item",
|
|
361
408
|
"gem-video",
|
|
@@ -768,6 +815,13 @@ var GEM_CARD_CSS_LINES = [
|
|
|
768
815
|
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-card='filled'] :is(.gem-section__inner, [class*='__inner']) { background: color-mix(in srgb, currentColor 6%, transparent); }"
|
|
769
816
|
];
|
|
770
817
|
var GEM_CARD_CSS = GEM_CARD_CSS_LINES.join("\n");
|
|
818
|
+
var GEM_COUNTDOWN_ENDED_CSS_LINES = [
|
|
819
|
+
":root:not(:has([data-gem-canvas])) [data-gem-section-id][data-gem-countdown-ended='true'] { display: none; }",
|
|
820
|
+
":root:has([data-gem-canvas]) [data-gem-section-id][data-gem-countdown-ended='true'] { opacity: .55; }",
|
|
821
|
+
".gem-countdown-ended-note { display: none; }",
|
|
822
|
+
":root:has([data-gem-canvas]) [data-gem-countdown-ended='true'] .gem-countdown-ended-note { display: block; margin: 0; padding: .25rem 0; font-size: .75rem; font-weight: 700; text-align: center; }"
|
|
823
|
+
];
|
|
824
|
+
var GEM_COUNTDOWN_ENDED_CSS = GEM_COUNTDOWN_ENDED_CSS_LINES.join("\n");
|
|
771
825
|
var GEM_REVEAL_CSS_LINES = [
|
|
772
826
|
// Longhand, so the per-element duration/delay knobs can reach it. The shorthand hard-coded 600ms,
|
|
773
827
|
// while `gem_animation_duration` and `gem_animation_delay` are declared on ALL FIFTY kinds in the
|
|
@@ -891,9 +945,26 @@ function createGemBaseCss(profile = "dawn-parity") {
|
|
|
891
945
|
"a {",
|
|
892
946
|
" color: inherit;",
|
|
893
947
|
"}",
|
|
948
|
+
// Vòng focus theo NỀN (gemsite #761, review #655; đo Chromium 03/10/2026).
|
|
949
|
+
// 1. --gem-focus-ring khai ở :root là "2px solid var(--gem-color-accent)" và var() bị thay MỘT LẦN ở :root: section
|
|
950
|
+
// đặt lại accent (bảng màu) vẫn nhận accent của trang. Khai lại trên chính section để var() tính theo section.
|
|
951
|
+
// 2. Núm nền accent / dark tô nền mà KHÔNG đổi accent: vòng accent trên nền accent là không thấy (1:1). Trong băng đó
|
|
952
|
+
// vòng HAI MÀU (WCAG C40): nét màu chữ của băng + quầng màu nền của băng bao quanh. Hai màu tương phản nhau nên
|
|
953
|
+
// trên nền nào cũng có một màu nổi: băng tối thì nét sáng, ô sáng lồng trong băng tối thì quầng tối. Màu chữ của
|
|
954
|
+
// khối chứa KHÔNG lấy được bằng CSS: currentColor giữ nguyên từ khoá tới phần tử dùng nó (kể cả biến có kiểu
|
|
955
|
+
// @property), nên nút / ô nhập ra vòng màu chữ của chính mặt nút (đen trên băng tối, đo).
|
|
956
|
+
// Đặt --gem-focus-ring ở :root không tới được trong section.
|
|
957
|
+
":where(.gem-section, [data-gem-section-id]) {",
|
|
958
|
+
" --gem-focus-ring: 2px solid var(--gem-color-accent);",
|
|
959
|
+
"}",
|
|
894
960
|
":focus-visible {",
|
|
895
961
|
" outline: var(--gem-focus-ring, 2px solid var(--gem-color-accent));",
|
|
896
|
-
" outline-offset: 3px;",
|
|
962
|
+
" outline-offset: var(--gem-focus-offset, 3px);",
|
|
963
|
+
"}",
|
|
964
|
+
// :focus-visible hai lần = độ ưu tiên (0,2,0): thắng `.gem-button { box-shadow: none }` của profile Dawn (0,1,0, nằm
|
|
965
|
+
// sau) đã xoá quầng của nút trên ô sáng lồng trong băng (đo); luật focus riêng của section (≥ 0,3,0) vẫn thắng nó.
|
|
966
|
+
":where([data-gem-section-background='accent'], [data-gem-section-background='dark']) :focus-visible:focus-visible {",
|
|
967
|
+
" box-shadow: 0 0 0 calc(var(--gem-focus-offset, 3px) + 4px) var(--gem-band-bg);",
|
|
897
968
|
"}",
|
|
898
969
|
".gem-skip-to-content-link:focus {",
|
|
899
970
|
" position: fixed;",
|
|
@@ -1105,6 +1176,56 @@ function createPrimitiveCss() {
|
|
|
1105
1176
|
" color: var(--gem-color-text);",
|
|
1106
1177
|
" padding: 0 var(--gem-space-md);",
|
|
1107
1178
|
"}",
|
|
1179
|
+
// .gem-field-join (gemfactory request #8, 01/10/2026; 16 stored footers trimmed the touching corners by hand): an
|
|
1180
|
+
// input/select and its button as one block (newsletter, discount code). Only VISIBLE children count: Shopify forms carry hidden inputs (form_type, utf8) and a sr-only
|
|
1181
|
+
// label, and with plain :first-child those would square the visible outer corners and take a 44px slot. A field
|
|
1182
|
+
// may be wrapped in .gem-form-field. The field grows with a floor of min(10rem, 50%); the button sizes to its
|
|
1183
|
+
// label (dropping dawn-parity's 12rem minimum, which at 320px left the field 80px wide) and wraps a long label
|
|
1184
|
+
// instead of squeezing the field or overflowing. Only the corners where two children meet are squared, in logical
|
|
1185
|
+
// properties so it mirrors under RTL, and the seam overlaps by one border width so it is not drawn twice. A child
|
|
1186
|
+
// holding focus is lifted so its ring is not painted under the neighbour. Stays one row at every width.
|
|
1187
|
+
".gem-field-join {",
|
|
1188
|
+
" display: flex;",
|
|
1189
|
+
" align-items: stretch;",
|
|
1190
|
+
" min-width: 0;",
|
|
1191
|
+
"}",
|
|
1192
|
+
'.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden) {',
|
|
1193
|
+
" min-height: var(--gem-component-input-height, 44px);",
|
|
1194
|
+
" margin: 0;",
|
|
1195
|
+
"}",
|
|
1196
|
+
".gem-field-join > :is(.gem-input, .gem-select, .gem-form-field) {",
|
|
1197
|
+
" flex: 1 1 10rem;",
|
|
1198
|
+
" width: auto;",
|
|
1199
|
+
" min-width: min(10rem, 50%);",
|
|
1200
|
+
"}",
|
|
1201
|
+
// A wrapped field gets the join's height too: the dawn-parity profile sets .gem-input { min-height: 4.5rem }, and
|
|
1202
|
+
// only the direct-child rule above outranked it, so a wrapped input stood 72px next to a 44px one (02/10/2026).
|
|
1203
|
+
".gem-field-join > .gem-form-field > :is(.gem-input, .gem-select) {",
|
|
1204
|
+
" width: 100%;",
|
|
1205
|
+
" height: 100%;",
|
|
1206
|
+
" min-height: var(--gem-component-input-height, 44px);",
|
|
1207
|
+
"}",
|
|
1208
|
+
'.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(.gem-input, .gem-select, .gem-form-field) {',
|
|
1209
|
+
" flex: 0 1 auto;",
|
|
1210
|
+
" min-width: var(--gem-component-input-height, 44px);",
|
|
1211
|
+
" white-space: normal;",
|
|
1212
|
+
" overflow-wrap: anywhere;",
|
|
1213
|
+
"}",
|
|
1214
|
+
'.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(:nth-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))), .gem-field-join > .gem-form-field:not(:nth-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))) > :is(.gem-input, .gem-select) {',
|
|
1215
|
+
" border-start-start-radius: 0;",
|
|
1216
|
+
" border-end-start-radius: 0;",
|
|
1217
|
+
"}",
|
|
1218
|
+
'.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(:nth-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))) {',
|
|
1219
|
+
" margin-inline-start: calc(-1 * var(--gem-input-border-width, 1px));",
|
|
1220
|
+
"}",
|
|
1221
|
+
'.gem-field-join > :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden):not(:nth-last-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))), .gem-field-join > .gem-form-field:not(:nth-last-child(1 of :not([type="hidden"]):not([hidden]):not(.gem-u-visually-hidden))) > :is(.gem-input, .gem-select) {',
|
|
1222
|
+
" border-start-end-radius: 0;",
|
|
1223
|
+
" border-end-end-radius: 0;",
|
|
1224
|
+
"}",
|
|
1225
|
+
".gem-field-join > :focus-within {",
|
|
1226
|
+
" position: relative;",
|
|
1227
|
+
" z-index: 1;",
|
|
1228
|
+
"}",
|
|
1108
1229
|
".gem-textarea {",
|
|
1109
1230
|
" min-height: calc(var(--gem-component-input-height, 44px) * 2);",
|
|
1110
1231
|
" padding-block: var(--gem-space-sm);",
|
|
@@ -1141,14 +1262,26 @@ function createPrimitiveCss() {
|
|
|
1141
1262
|
" color: var(--gem-color-muted, color-mix(in srgb, var(--gem-color-text) 72%, transparent));",
|
|
1142
1263
|
" line-height: 1.6;",
|
|
1143
1264
|
"}",
|
|
1265
|
+
// Icon size and colour hooks (gemfactory request #11, 01/10/2026: 33 sections reset colour to inherit and wrote
|
|
1266
|
+
// 22/14/16/20px by hand). Additive only: with no modifier and no --gem-icon-size an icon renders exactly as before
|
|
1267
|
+
// (the spacing step, the accent). Changing those DEFAULTS is a design decision left open on #11.
|
|
1144
1268
|
".gem-icon {",
|
|
1145
1269
|
" display: inline-flex;",
|
|
1146
1270
|
" align-items: center;",
|
|
1147
1271
|
" justify-content: center;",
|
|
1148
|
-
" inline-size: var(--gem-space-lg);",
|
|
1149
|
-
" block-size: var(--gem-space-lg);",
|
|
1272
|
+
" inline-size: var(--gem-icon-size, var(--gem-space-lg));",
|
|
1273
|
+
" block-size: var(--gem-icon-size, var(--gem-space-lg));",
|
|
1150
1274
|
" color: var(--gem-color-accent);",
|
|
1151
1275
|
"}",
|
|
1276
|
+
".gem-icon--sm { --gem-icon-size: 16px; }",
|
|
1277
|
+
".gem-icon--md { --gem-icon-size: 20px; }",
|
|
1278
|
+
".gem-icon--lg { --gem-icon-size: 24px; }",
|
|
1279
|
+
".gem-icon--current { color: currentColor; }",
|
|
1280
|
+
".gem-icon--chevron { transition: rotate var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease); }",
|
|
1281
|
+
// Only the TOGGLE's own state: the runtime also sets aria-expanded on containers (the open cart drawer, the
|
|
1282
|
+
// header drawer), and a descendant match would flip every chevron inside them (review #650).
|
|
1283
|
+
":is(button, summary, [role='button'])[aria-expanded='true'] > .gem-icon--chevron, details[open] > summary > .gem-icon--chevron { rotate: 180deg; }",
|
|
1284
|
+
"@media (prefers-reduced-motion: reduce) { .gem-icon--chevron { transition: none; } }",
|
|
1152
1285
|
".gem-icon-list {",
|
|
1153
1286
|
" display: grid;",
|
|
1154
1287
|
" gap: var(--gem-space-sm);",
|
|
@@ -1856,6 +1989,7 @@ function createPrimitiveCss() {
|
|
|
1856
1989
|
...GEM_AMBIENT_CSS_LINES,
|
|
1857
1990
|
...GEM_DIVIDER_CSS_LINES,
|
|
1858
1991
|
...GEM_CARD_CSS_LINES,
|
|
1992
|
+
...GEM_COUNTDOWN_ENDED_CSS_LINES,
|
|
1859
1993
|
".gem-promo-banner, .gem-promo-tile {",
|
|
1860
1994
|
" border: var(--gem-border-subtle, 1px solid var(--gem-color-border));",
|
|
1861
1995
|
" border-radius: var(--gem-radius-md);",
|
|
@@ -1959,6 +2093,18 @@ function createPrimitiveCss() {
|
|
|
1959
2093
|
" inset: 0;",
|
|
1960
2094
|
" background: rgba(0, 0, 0, 0.36);",
|
|
1961
2095
|
"}",
|
|
2096
|
+
// Opening motion (gemfactory request #13, 01/10/2026): 28 stored drawers each declared their own
|
|
2097
|
+
// `@keyframes …-in { from { transform: translateX(24px); opacity: 0 } }`. OPT-IN on the drawer root,
|
|
2098
|
+
// data-gem-drawer-motion="slide", so no existing drawer changes (review #650: as a default the overlay fade
|
|
2099
|
+
// reached all 28): the panel slides in by --gem-drawer-offset (24px) and the overlay fades. A drawer drops its own
|
|
2100
|
+
// keyframes by adding the attribute. The panel sits at inline-end, so under RTL it comes from the left.
|
|
2101
|
+
// :dir(rtl) follows the HTML dir attribute through inheritance, so a dir="ltr" island inside an rtl page stays
|
|
2102
|
+
// ltr (a `[dir='rtl'] .x` descendant rule got that wrong). CSS `direction` alone is invisible to selectors. Off under prefers-reduced-motion.
|
|
2103
|
+
"@keyframes gem-cart-drawer-panel-in { from { translate: calc(var(--gem-drawer-offset, 24px) * var(--gem-drawer-dir, 1)) 0; opacity: 0; } }",
|
|
2104
|
+
"@keyframes gem-cart-drawer-overlay-in { from { opacity: 0; } }",
|
|
2105
|
+
".gem-cart-drawer__panel:dir(rtl) { --gem-drawer-dir: -1; }",
|
|
2106
|
+
".gem-cart-drawer[data-gem-drawer-motion='slide'] .gem-cart-drawer__overlay { animation: gem-cart-drawer-overlay-in var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease); }",
|
|
2107
|
+
".gem-cart-drawer[data-gem-drawer-motion='slide'] .gem-cart-drawer__panel { animation: gem-cart-drawer-panel-in var(--gem-motion-duration, 160ms) var(--gem-motion-easing, ease); }",
|
|
1962
2108
|
".gem-cart-drawer__panel {",
|
|
1963
2109
|
" position: fixed;",
|
|
1964
2110
|
" inset-block: 0;",
|
|
@@ -1971,6 +2117,9 @@ function createPrimitiveCss() {
|
|
|
1971
2117
|
" box-shadow: var(--gem-shadow-md, none);",
|
|
1972
2118
|
" padding: var(--gem-space-lg);",
|
|
1973
2119
|
"}",
|
|
2120
|
+
// AFTER the opt-in rules and at THEIR specificity: measured 02/10/2026, a reduced-motion rule placed before the
|
|
2121
|
+
// animation rule (or weaker than it) left the panel animating.
|
|
2122
|
+
"@media (prefers-reduced-motion: reduce) { .gem-cart-drawer[data-gem-drawer-motion] :is(.gem-cart-drawer__panel, .gem-cart-drawer__overlay) { animation: none; } }",
|
|
1974
2123
|
".gem-cart-drawer__header, .gem-cart-drawer__footer {",
|
|
1975
2124
|
" display: flex;",
|
|
1976
2125
|
" flex-wrap: wrap;",
|
|
@@ -2346,6 +2495,7 @@ function createGemSectionRuntimeContractCss(selector = "[data-gem-section-librar
|
|
|
2346
2495
|
}
|
|
2347
2496
|
var OPT_IN_EYEBROW = ":where(.gem-section[data-gem-eyebrow='show']) .gem-section__eyebrow { display: block; letter-spacing: 0.12em; }";
|
|
2348
2497
|
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; }";
|
|
2498
|
+
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); }";
|
|
2349
2499
|
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)); }";
|
|
2350
2500
|
function createSectionCss() {
|
|
2351
2501
|
return [
|
|
@@ -2492,8 +2642,8 @@ function createSectionCss() {
|
|
|
2492
2642
|
// rather than re-deriving the swatch in every divider rule.
|
|
2493
2643
|
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='default'] { --gem-section-bg: var(--gem-color-background); background: transparent; }",
|
|
2494
2644
|
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='surface'] { --gem-section-bg: var(--gem-color-surface); background: var(--gem-section-bg); padding-block: var(--gem-space-xl); }",
|
|
2495
|
-
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='accent'] { --gem-section-bg: var(--gem-color-accent); background: var(--gem-section-bg); color: var(--gem-color-accent-text); padding-block: var(--gem-space-xl); }",
|
|
2496
|
-
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='dark'] { --gem-section-bg: var(--gem-color-text); background: var(--gem-section-bg); color: var(--gem-color-background); padding-block: var(--gem-space-xl); }",
|
|
2645
|
+
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='accent'] { --gem-section-bg: var(--gem-color-accent); background: var(--gem-section-bg); color: var(--gem-color-accent-text); --gem-band-bg: var(--gem-color-accent); --gem-focus-ring: 2px solid var(--gem-color-accent-text); padding-block: var(--gem-space-xl); }",
|
|
2646
|
+
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='dark'] { --gem-section-bg: var(--gem-color-text); background: var(--gem-section-bg); color: var(--gem-color-background); --gem-band-bg: var(--gem-color-text); --gem-focus-ring: 2px solid var(--gem-color-background); padding-block: var(--gem-space-xl); }",
|
|
2497
2647
|
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='light'] { --gem-section-bg: var(--gem-color-background); background: var(--gem-section-bg); color: var(--gem-color-text); padding-block: var(--gem-space-xl); }",
|
|
2498
2648
|
// On accent/dark bands, keep muted sub-copy + headings legible against the inverted ground.
|
|
2499
2649
|
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='accent'] :is(.gem-section__heading, .gem-section__body, .gem-section__eyebrow), :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-section-background='dark'] :is(.gem-section__heading, .gem-section__body, .gem-section__eyebrow) { color: inherit; }",
|
|
@@ -2667,6 +2817,7 @@ function createSectionCss() {
|
|
|
2667
2817
|
"}",
|
|
2668
2818
|
OPT_IN_EYEBROW,
|
|
2669
2819
|
OPT_IN_HEADING,
|
|
2820
|
+
OPT_IN_BODY,
|
|
2670
2821
|
"@media (max-width: 760px) {",
|
|
2671
2822
|
OPT_IN_HEADING_MOBILE,
|
|
2672
2823
|
"}",
|
|
@@ -2917,6 +3068,7 @@ function createDawnParityCss() {
|
|
|
2917
3068
|
// Opt-ins again, AFTER the Dawn rules above (see OPT_IN_EYEBROW).
|
|
2918
3069
|
OPT_IN_EYEBROW,
|
|
2919
3070
|
OPT_IN_HEADING,
|
|
3071
|
+
OPT_IN_BODY,
|
|
2920
3072
|
createDawnParityMobileCss(),
|
|
2921
3073
|
// The row again, after every Dawn rule including its phone block (see GEM_ROW_CSS_LINES).
|
|
2922
3074
|
...GEM_ROW_CSS_LINES
|
|
@@ -4335,37 +4487,64 @@ var beforeAfterPluginRegistrationLines = [
|
|
|
4335
4487
|
" api.emit('gem:before-after:ready', { source: 'gem-runtime', count: bound });",
|
|
4336
4488
|
" });"
|
|
4337
4489
|
];
|
|
4490
|
+
var COUNTDOWN_END_RUNTIME_LINES = [
|
|
4491
|
+
" const COUNTDOWN_ISO = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/i;",
|
|
4492
|
+
" const countdownEnd = (value) => {",
|
|
4493
|
+
" if (typeof value !== 'string') return NaN;",
|
|
4494
|
+
" const raw = value.trim();",
|
|
4495
|
+
" const m = COUNTDOWN_ISO.exec(raw);",
|
|
4496
|
+
" if (!m) return NaN;",
|
|
4497
|
+
" const y = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);",
|
|
4498
|
+
" const day = new Date(Date.UTC(y, mo - 1, d));",
|
|
4499
|
+
" if (day.getUTCFullYear() !== y || day.getUTCMonth() !== mo - 1 || day.getUTCDate() !== d) return NaN;",
|
|
4500
|
+
" if (m[4] === undefined) return Date.parse(m[1] + '-' + m[2] + '-' + m[3] + 'T23:59:59Z');",
|
|
4501
|
+
" const iso = m[1] + '-' + m[2] + '-' + m[3] + 'T' + raw.slice(11);",
|
|
4502
|
+
" return Date.parse(m[7] ? iso : iso + 'Z');",
|
|
4503
|
+
" };"
|
|
4504
|
+
];
|
|
4338
4505
|
var countdownPluginRegistrationLines = [
|
|
4339
4506
|
" registerPlugin('countdown', ({ api }) => {",
|
|
4340
4507
|
" const timers = document.querySelectorAll('[data-gem-countdown], .gem-countdown');",
|
|
4341
4508
|
" let bound = 0;",
|
|
4342
4509
|
" const pad = (value) => String(Math.max(0, value)).padStart(2, '0');",
|
|
4510
|
+
...COUNTDOWN_END_RUNTIME_LINES.map((line) => " " + line),
|
|
4343
4511
|
" const write = (timer, selector, value) => {",
|
|
4344
4512
|
" const targetNode = timer.querySelector && timer.querySelector(selector);",
|
|
4345
4513
|
" if (targetNode) targetNode.textContent = pad(value);",
|
|
4346
4514
|
" };",
|
|
4347
4515
|
" timers.forEach((timer) => {",
|
|
4348
4516
|
" if (!mark(timer, 'countdown')) return;",
|
|
4349
|
-
" const
|
|
4350
|
-
" const
|
|
4517
|
+
" const targetTime = countdownEnd(timer.getAttribute && timer.getAttribute('data-gem-countdown-target'));",
|
|
4518
|
+
" const section = timer.closest ? timer.closest('[data-gem-section-id]') : null;",
|
|
4519
|
+
" const band = section && section.hasAttribute && section.hasAttribute('data-gem-countdown-end') ? section : null;",
|
|
4520
|
+
" let handle = null;",
|
|
4351
4521
|
" const update = () => {",
|
|
4352
4522
|
" if (!Number.isFinite(targetTime)) {",
|
|
4353
4523
|
" timer.dataset.gemCountdownState = 'invalid';",
|
|
4354
4524
|
" return;",
|
|
4355
4525
|
" }",
|
|
4356
|
-
" const
|
|
4526
|
+
" const ms = targetTime - Date.now();",
|
|
4527
|
+
" const remainingSeconds = Math.max(0, Math.floor(ms / 1000));",
|
|
4357
4528
|
" const days = Math.floor(remainingSeconds / 86400);",
|
|
4358
4529
|
" const hours = Math.floor((remainingSeconds % 86400) / 3600);",
|
|
4359
4530
|
" const minutes = Math.floor((remainingSeconds % 3600) / 60);",
|
|
4360
|
-
" timer.dataset.gemCountdownState =
|
|
4531
|
+
" timer.dataset.gemCountdownState = ms > 0 ? 'running' : 'complete';",
|
|
4361
4532
|
" timer.dataset.gemCountdownRemainingSeconds = String(remainingSeconds);",
|
|
4362
4533
|
" write(timer, '[data-gem-countdown-days]', days);",
|
|
4363
4534
|
" write(timer, '[data-gem-countdown-hours]', hours);",
|
|
4364
4535
|
" write(timer, '[data-gem-countdown-minutes]', minutes);",
|
|
4365
|
-
"
|
|
4536
|
+
" if (ms <= 0) {",
|
|
4537
|
+
" if (band) band.setAttribute('data-gem-countdown-ended', 'true');",
|
|
4538
|
+
" if (handle !== null && target.clearInterval) { target.clearInterval(handle); handle = null; }",
|
|
4539
|
+
" }",
|
|
4540
|
+
" api.emit(ms > 0 ? 'gem:countdown:start' : 'gem:countdown:complete', { source: 'gem-runtime', id: timer.id || undefined, remainingSeconds });",
|
|
4366
4541
|
" };",
|
|
4367
4542
|
" update();",
|
|
4368
|
-
" if (target.setInterval && Number.isFinite(targetTime)) target.setInterval(update, 60000);",
|
|
4543
|
+
" if (target.setInterval && Number.isFinite(targetTime) && targetTime - Date.now() > 0) handle = target.setInterval(update, 60000);",
|
|
4544
|
+
// The readout moves once a minute, but the END is not allowed to wait for the next minute: one timer
|
|
4545
|
+
// for the exact instant (while it fits a setTimeout, ~24.8 days; past that the minute tick gets there).
|
|
4546
|
+
" const left = targetTime - Date.now();",
|
|
4547
|
+
" if (target.setTimeout && Number.isFinite(left) && left > 0 && left < 2147483647) target.setTimeout(update, left);",
|
|
4369
4548
|
" bound += 1;",
|
|
4370
4549
|
" });",
|
|
4371
4550
|
" api.emit('gem:countdown:ready', { source: 'gem-runtime', count: bound });",
|
|
@@ -4717,6 +4896,12 @@ var canonicalCssVariableNames = /* @__PURE__ */ new Set([
|
|
|
4717
4896
|
// --gem-focus-ring: emitted with the tokens, not a group (tokens.ts GEM_FOCUS_RING_CSS_VAR).
|
|
4718
4897
|
GEM_FOCUS_RING_CSS_VAR.split(":")[0].trim(),
|
|
4719
4898
|
GEM_TAP_MIN_CSS_VAR.split(":")[0].trim(),
|
|
4899
|
+
GEM_FOCUS_OFFSET_CSS_VAR.split(":")[0].trim(),
|
|
4900
|
+
// The derived spacing steps (tokens.ts GEM_DERIVED_SPACE_STEPS): always emitted, never required.
|
|
4901
|
+
...GEM_DERIVED_SPACE_CSS_VARIABLES,
|
|
4902
|
+
// Extension points a section SETS (gemtheme #650): icon size, drawer slide distance.
|
|
4903
|
+
"--gem-icon-size",
|
|
4904
|
+
"--gem-drawer-offset",
|
|
4720
4905
|
// The row primitive's variables (primitives.ts GEM_ROW_CSS). Sections SET these on their grid.
|
|
4721
4906
|
...GEM_ROW_CSS_VARIABLES
|
|
4722
4907
|
]);
|
|
@@ -5997,7 +6182,9 @@ function stringSetting(value) {
|
|
|
5997
6182
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
5998
6183
|
}
|
|
5999
6184
|
export {
|
|
6185
|
+
COUNTDOWN_END_RUNTIME_LINES,
|
|
6000
6186
|
GEM_AMBIENT_CSS,
|
|
6187
|
+
GEM_COUNTDOWN_ENDED_CSS,
|
|
6001
6188
|
GEM_GRID_COLUMNS_ROW_KNOB_CSS,
|
|
6002
6189
|
GEM_PART_CLASS,
|
|
6003
6190
|
GEM_REVEAL_CSS,
|
package/dist/section-gate.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// ../../packages/gem-framework/src/tokens.ts
|
|
2
2
|
var GEM_FOCUS_RING_CSS_VAR = "--gem-focus-ring: 2px solid var(--gem-color-accent);";
|
|
3
3
|
var GEM_TAP_MIN_CSS_VAR = "--gem-tap-min: 44px;";
|
|
4
|
+
var GEM_FOCUS_OFFSET_CSS_VAR = "--gem-focus-offset: 3px;";
|
|
5
|
+
var GEM_DERIVED_SPACE_STEPS = { "2xs": 0.125, ms: 0.75, "4xl": 7 };
|
|
6
|
+
var GEM_DERIVED_SPACE_CSS_VARIABLES = Object.keys(GEM_DERIVED_SPACE_STEPS).map((key) => `--gem-space-${slugify(key)}`);
|
|
4
7
|
var GEM_ROW_CSS_VARIABLES = [
|
|
5
8
|
"--gem-row-cols",
|
|
6
9
|
"--gem-row-cols-md",
|
|
@@ -23,10 +26,19 @@ var requiredGemTokenKeys = {
|
|
|
23
26
|
component: ["headerHeight", "cardPadding", "buttonPaddingInline", "buttonPaddingBlock", "inputHeight"],
|
|
24
27
|
morphology: ["campaignDensity", "mediaRhythm", "proofIntensity", "commerceEmphasis", "policyTone"]
|
|
25
28
|
};
|
|
26
|
-
|
|
29
|
+
var rampKey = (k) => k.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
30
|
+
var LENGTH = /^\d*\.?\d+(px|rem|em)$/;
|
|
31
|
+
var NUMBER = /^\d*\.?\d+$/;
|
|
32
|
+
var typeRampValue = (typography, key) => {
|
|
33
|
+
const hit = Object.entries(typography ?? {}).find(([k]) => rampKey(k) === rampKey(key));
|
|
34
|
+
const v = hit == null || hit[1] == null ? "" : stripImportant(hit[1]).trim();
|
|
35
|
+
if (!v) return null;
|
|
36
|
+
return (key === "scale" ? NUMBER.test(v) && Number(v) > 0 : LENGTH.test(v)) ? v : null;
|
|
37
|
+
};
|
|
38
|
+
function gemTypeRampCssVars(typography) {
|
|
27
39
|
return [
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
`--gem-type-base: ${typeRampValue(typography, "base") ?? "1rem"};`,
|
|
41
|
+
`--gem-type-scale: ${typeRampValue(typography, "scale") ?? "1.22"};`,
|
|
30
42
|
"--gem-text-sm: calc(var(--gem-type-base) / var(--gem-type-scale));",
|
|
31
43
|
"--gem-body: var(--gem-type-base);",
|
|
32
44
|
"--gem-h6: calc(var(--gem-type-base) * var(--gem-type-scale));",
|
|
@@ -37,6 +49,11 @@ function gemTypeRampCssVars() {
|
|
|
37
49
|
"--gem-h1: calc(var(--gem-h2) * var(--gem-type-scale));"
|
|
38
50
|
];
|
|
39
51
|
}
|
|
52
|
+
function stripImportant(value) {
|
|
53
|
+
const s = String(value);
|
|
54
|
+
if (!/!\s*important/i.test(s)) return s;
|
|
55
|
+
return s.replace(/!\s*important/gi, " ").replace(/\s+/g, " ").replace(/\s+,/g, ",").trim();
|
|
56
|
+
}
|
|
40
57
|
function slugify(value) {
|
|
41
58
|
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
42
59
|
}
|
|
@@ -60,7 +77,9 @@ var gemFrameworkPrimitiveGroups = [
|
|
|
60
77
|
},
|
|
61
78
|
{
|
|
62
79
|
group: "surfaces",
|
|
63
|
-
|
|
80
|
+
// `gem-countdown-ended-note`: the editor-canvas line every countdown band carries, styled once in
|
|
81
|
+
// primitives.ts GEM_COUNTDOWN_ENDED_CSS (hidden on the store, shown on an ended band in the canvas).
|
|
82
|
+
classes: ["gem-card", "gem-media", "gem-badge", "gem-accordion", "gem-divider", "gem-countdown-ended-note"]
|
|
64
83
|
},
|
|
65
84
|
{
|
|
66
85
|
group: "forms",
|
|
@@ -71,7 +90,9 @@ var gemFrameworkPrimitiveGroups = [
|
|
|
71
90
|
"gem-select",
|
|
72
91
|
"gem-textarea",
|
|
73
92
|
"gem-submit",
|
|
74
|
-
"gem-newsletter-form"
|
|
93
|
+
"gem-newsletter-form",
|
|
94
|
+
// Input + button as one control (primitives.ts, gemfactory request #8).
|
|
95
|
+
"gem-field-join"
|
|
75
96
|
]
|
|
76
97
|
},
|
|
77
98
|
{
|
|
@@ -83,6 +104,12 @@ var gemFrameworkPrimitiveGroups = [
|
|
|
83
104
|
// storefront alongside gem-section__ctas.
|
|
84
105
|
"gem-prose",
|
|
85
106
|
"gem-icon",
|
|
107
|
+
// Size / colour / rotate hooks (gemfactory request #11); the bare class renders as before.
|
|
108
|
+
"gem-icon--sm",
|
|
109
|
+
"gem-icon--md",
|
|
110
|
+
"gem-icon--lg",
|
|
111
|
+
"gem-icon--current",
|
|
112
|
+
"gem-icon--chevron",
|
|
86
113
|
"gem-icon-list",
|
|
87
114
|
"gem-icon-list__item",
|
|
88
115
|
"gem-video",
|
|
@@ -356,6 +383,13 @@ var GEM_CARD_CSS_LINES = [
|
|
|
356
383
|
":is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow, .gem-footer)[data-gem-card='filled'] :is(.gem-section__inner, [class*='__inner']) { background: color-mix(in srgb, currentColor 6%, transparent); }"
|
|
357
384
|
];
|
|
358
385
|
var GEM_CARD_CSS = GEM_CARD_CSS_LINES.join("\n");
|
|
386
|
+
var GEM_COUNTDOWN_ENDED_CSS_LINES = [
|
|
387
|
+
":root:not(:has([data-gem-canvas])) [data-gem-section-id][data-gem-countdown-ended='true'] { display: none; }",
|
|
388
|
+
":root:has([data-gem-canvas]) [data-gem-section-id][data-gem-countdown-ended='true'] { opacity: .55; }",
|
|
389
|
+
".gem-countdown-ended-note { display: none; }",
|
|
390
|
+
":root:has([data-gem-canvas]) [data-gem-countdown-ended='true'] .gem-countdown-ended-note { display: block; margin: 0; padding: .25rem 0; font-size: .75rem; font-weight: 700; text-align: center; }"
|
|
391
|
+
];
|
|
392
|
+
var GEM_COUNTDOWN_ENDED_CSS = GEM_COUNTDOWN_ENDED_CSS_LINES.join("\n");
|
|
359
393
|
var GEM_REVEAL_CSS_LINES = [
|
|
360
394
|
// Longhand, so the per-element duration/delay knobs can reach it. The shorthand hard-coded 600ms,
|
|
361
395
|
// while `gem_animation_duration` and `gem_animation_delay` are declared on ALL FIFTY kinds in the
|
|
@@ -463,6 +497,69 @@ function offerEngineRuntimeScript() {
|
|
|
463
497
|
}
|
|
464
498
|
|
|
465
499
|
// ../../packages/gem-framework/src/runtime.ts
|
|
500
|
+
var COUNTDOWN_END_RUNTIME_LINES = [
|
|
501
|
+
" const COUNTDOWN_ISO = /^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/i;",
|
|
502
|
+
" const countdownEnd = (value) => {",
|
|
503
|
+
" if (typeof value !== 'string') return NaN;",
|
|
504
|
+
" const raw = value.trim();",
|
|
505
|
+
" const m = COUNTDOWN_ISO.exec(raw);",
|
|
506
|
+
" if (!m) return NaN;",
|
|
507
|
+
" const y = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);",
|
|
508
|
+
" const day = new Date(Date.UTC(y, mo - 1, d));",
|
|
509
|
+
" if (day.getUTCFullYear() !== y || day.getUTCMonth() !== mo - 1 || day.getUTCDate() !== d) return NaN;",
|
|
510
|
+
" if (m[4] === undefined) return Date.parse(m[1] + '-' + m[2] + '-' + m[3] + 'T23:59:59Z');",
|
|
511
|
+
" const iso = m[1] + '-' + m[2] + '-' + m[3] + 'T' + raw.slice(11);",
|
|
512
|
+
" return Date.parse(m[7] ? iso : iso + 'Z');",
|
|
513
|
+
" };"
|
|
514
|
+
];
|
|
515
|
+
var countdownPluginRegistrationLines = [
|
|
516
|
+
" registerPlugin('countdown', ({ api }) => {",
|
|
517
|
+
" const timers = document.querySelectorAll('[data-gem-countdown], .gem-countdown');",
|
|
518
|
+
" let bound = 0;",
|
|
519
|
+
" const pad = (value) => String(Math.max(0, value)).padStart(2, '0');",
|
|
520
|
+
...COUNTDOWN_END_RUNTIME_LINES.map((line) => " " + line),
|
|
521
|
+
" const write = (timer, selector, value) => {",
|
|
522
|
+
" const targetNode = timer.querySelector && timer.querySelector(selector);",
|
|
523
|
+
" if (targetNode) targetNode.textContent = pad(value);",
|
|
524
|
+
" };",
|
|
525
|
+
" timers.forEach((timer) => {",
|
|
526
|
+
" if (!mark(timer, 'countdown')) return;",
|
|
527
|
+
" const targetTime = countdownEnd(timer.getAttribute && timer.getAttribute('data-gem-countdown-target'));",
|
|
528
|
+
" const section = timer.closest ? timer.closest('[data-gem-section-id]') : null;",
|
|
529
|
+
" const band = section && section.hasAttribute && section.hasAttribute('data-gem-countdown-end') ? section : null;",
|
|
530
|
+
" let handle = null;",
|
|
531
|
+
" const update = () => {",
|
|
532
|
+
" if (!Number.isFinite(targetTime)) {",
|
|
533
|
+
" timer.dataset.gemCountdownState = 'invalid';",
|
|
534
|
+
" return;",
|
|
535
|
+
" }",
|
|
536
|
+
" const ms = targetTime - Date.now();",
|
|
537
|
+
" const remainingSeconds = Math.max(0, Math.floor(ms / 1000));",
|
|
538
|
+
" const days = Math.floor(remainingSeconds / 86400);",
|
|
539
|
+
" const hours = Math.floor((remainingSeconds % 86400) / 3600);",
|
|
540
|
+
" const minutes = Math.floor((remainingSeconds % 3600) / 60);",
|
|
541
|
+
" timer.dataset.gemCountdownState = ms > 0 ? 'running' : 'complete';",
|
|
542
|
+
" timer.dataset.gemCountdownRemainingSeconds = String(remainingSeconds);",
|
|
543
|
+
" write(timer, '[data-gem-countdown-days]', days);",
|
|
544
|
+
" write(timer, '[data-gem-countdown-hours]', hours);",
|
|
545
|
+
" write(timer, '[data-gem-countdown-minutes]', minutes);",
|
|
546
|
+
" if (ms <= 0) {",
|
|
547
|
+
" if (band) band.setAttribute('data-gem-countdown-ended', 'true');",
|
|
548
|
+
" if (handle !== null && target.clearInterval) { target.clearInterval(handle); handle = null; }",
|
|
549
|
+
" }",
|
|
550
|
+
" api.emit(ms > 0 ? 'gem:countdown:start' : 'gem:countdown:complete', { source: 'gem-runtime', id: timer.id || undefined, remainingSeconds });",
|
|
551
|
+
" };",
|
|
552
|
+
" update();",
|
|
553
|
+
" if (target.setInterval && Number.isFinite(targetTime) && targetTime - Date.now() > 0) handle = target.setInterval(update, 60000);",
|
|
554
|
+
// The readout moves once a minute, but the END is not allowed to wait for the next minute: one timer
|
|
555
|
+
// for the exact instant (while it fits a setTimeout, ~24.8 days; past that the minute tick gets there).
|
|
556
|
+
" const left = targetTime - Date.now();",
|
|
557
|
+
" if (target.setTimeout && Number.isFinite(left) && left > 0 && left < 2147483647) target.setTimeout(update, left);",
|
|
558
|
+
" bound += 1;",
|
|
559
|
+
" });",
|
|
560
|
+
" api.emit('gem:countdown:ready', { source: 'gem-runtime', count: bound });",
|
|
561
|
+
" });"
|
|
562
|
+
];
|
|
466
563
|
var offerEnginePluginRegistrationLines = [
|
|
467
564
|
" registerPlugin('offer-engine', ({ api }) => {",
|
|
468
565
|
" " + offerEngineRuntimeScript(),
|
|
@@ -488,6 +585,12 @@ var canonicalCssVariableNames = /* @__PURE__ */ new Set([
|
|
|
488
585
|
// --gem-focus-ring: emitted with the tokens, not a group (tokens.ts GEM_FOCUS_RING_CSS_VAR).
|
|
489
586
|
GEM_FOCUS_RING_CSS_VAR.split(":")[0].trim(),
|
|
490
587
|
GEM_TAP_MIN_CSS_VAR.split(":")[0].trim(),
|
|
588
|
+
GEM_FOCUS_OFFSET_CSS_VAR.split(":")[0].trim(),
|
|
589
|
+
// The derived spacing steps (tokens.ts GEM_DERIVED_SPACE_STEPS): always emitted, never required.
|
|
590
|
+
...GEM_DERIVED_SPACE_CSS_VARIABLES,
|
|
591
|
+
// Extension points a section SETS (gemtheme #650): icon size, drawer slide distance.
|
|
592
|
+
"--gem-icon-size",
|
|
593
|
+
"--gem-drawer-offset",
|
|
491
594
|
// The row primitive's variables (primitives.ts GEM_ROW_CSS). Sections SET these on their grid.
|
|
492
595
|
...GEM_ROW_CSS_VARIABLES
|
|
493
596
|
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gemboss/gem-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|