@gemboss/gem-framework 0.15.0 → 0.18.11

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
@@ -6,7 +6,7 @@ gem-framework của GemTheme đóng thành package, cho các repo **ngoài** mon
6
6
  | Cổng vào | Là gì | Nguồn |
7
7
  |---|---|---|
8
8
  | `@gemboss/gem-framework` | token, `createGemThemeCss`, runtime, manifest, bộ kiểm framework. Từ 0.3.4 manifest khai họ `gem-cart-line-item` (khối, `__media`, `__details`, `__title`, `__variant`, `__plan`, `__meta`, `__remove`, `__quantity`, `__price`): framework vốn style và adapter Shopify vốn render nó, nên section dùng markup dòng giỏ của framework không còn bị `unknown_gem_class` | `packages/gem-framework/src` |
9
- | `…/sections` | Các hàm `headless.tsx` được import: `elementOverrides`, `imageSrcSet`, `subscriptionPlans`, `defaultPlan`; từ 0.15.0 thêm bộ đọc nhấn trong câu `inlineMarkRuns`, `phraseMarkRuns`, `markClassName`, `inlineMarkText`, `safeMarkHref`, `MARK_KINDS` | `packages/gem-sections/src` |
9
+ | `…/sections` | Các hàm `headless.tsx` được import: `elementOverrides`, `imageSrcSet`, `subscriptionPlans`, `defaultPlan`; từ 0.15.0 thêm bộ đọc nhấn trong câu `inlineMarkRuns`, `phraseMarkRuns`, `markClassName`, `inlineMarkText`, `safeMarkHref`, `MARK_KINDS`; từ 0.16.0 thêm giá thẻ sản phẩm `cardPrice` (`card-price.ts`: nhiều giá thì "From" + giá thấp nhất, Sale chỉ khi biến thể mua được đang giảm, hết hàng, biến thể mà giá nói tới). `product-card-grid` quick-view-grid và featured từ section-library 1.39.6 import chúng: đồng bộ hai section đó khi đã lên 0.16.0 | `packages/gem-sections/src` |
10
10
  | `…/section-gate` | `validateSectionArtifacts`: ĐÚNG bộ kiểm section gemtheme dùng khi duyệt. Từ 0.3.0 thêm ba cổng của thư viện cho MỘT section: `layoutKnobCheck` (+ `LAYOUT_ATTRS`), `slotContractDrift` (+ `slotsReadBy`, `slotSourceFromElementTree`), `cssPairingOrphans` (+ `classesStyledBy`, truyền CSS từ `createGemThemeCss`). Từ 0.3.2 thêm hai cổng pháp lý: `fabricatedProofInSource` / `fabricatedProofInSchema` (điểm, số lượt, người, lời hứa bịa) và `inventedTestimonyIn` (hằng DEMO_*_QUOTE/REVIEW… làm giá trị khi thiếu dữ liệu). Từ 0.3.3 hai cổng này đọc được tiếng Việt ("4,8/5", "đã bán", "miễn phí vận chuyển", "199k", cả chữ NFD), tra giá trị của `DEMO.<key>` ở mọi chỗ đọc nó (`??`, `\|\|`, toán tử ba ngôi, dòng bị Prettier ngắt), và bỏ qua số lượng hiển thị (`reviews_to_show`…). Nâng từ 0.3.2 lên có thể làm đỏ section trước đó xanh: đó là lời khẳng định nằm trong DEMO. Từ 0.3.5 bắt thêm chữ ký người Việt (tên kèm nơi chốn như "Chị Lan, Hà Nội", hoặc họ + Thị/Văn như "Nguyễn Thị Hoa"; tiêu đề viết hoa từng chữ thì không), điểm sao ("4,9 ★"), `rating: 4.9` / `reviewCount: "1.284"` trong mảng sản phẩm mẫu, và hằng demo ở nhánh else của toán tử ba ngôi (`bound ? [] : DEMO_REVIEWS`). Mỗi thứ bịa chỉ báo MỘT lần: tại dòng DEMO nếu dòng đó tự lộ, không thì tại chỗ đọc. "đồng hồ" không còn bị đọc là giá. Từ 0.3.6 bắt lời hứa có thời hạn đứng sau từ khoá ("Dùng thử 30 ngày", "Bảo hành 12 tháng", "Nhận trong 2–4 ngày", "Giao 2 giờ") và "hoàn tiền" không kèm số; "Chính sách hoàn tiền" vẫn là nhãn. Từ 0.3.7 câu hỏi (kết thúc bằng "?") không bao giờ là lời hứa, "không hợp lệ"/số bước/nhịp giao không bị báo, và bắt thêm "trong vòng N ngày", "Giao hàng 2–4 ngày", "Bảo hành 1 đổi 1…", "Try it free for 30 days", "Ships in 24h". Từ 0.3.8 xuất thêm `claimIn` (câu hỏi "một chuỗi này có phải lời khẳng định, người bịa, hay lời hứa?"). Từ 0.3.9 bắt "cancel anytime", "guaranteed delivery", "huỷ bất cứ lúc nào" (chính sách huỷ gói và giao hàng). Từ 0.3.10 lời từ chối ("cannot be canceled", "Không thể hủy") không bị báo, "giao dịch/giao diện" không phải giao hàng, "Delivery not guaranteed" là lời miễn trừ, một chuỗi chỉ gồm con số viết tắt ("199k", "12k+") mang nhãn trung tính "hardcoded figure" (không phân biệt được giá với số đếm), và `stringLiteralsIn(line)` thay cho hằng `STRING_LITERAL` (đã gỡ: một regex `/g` dùng chung rò `lastIndex`; 0 chỗ dùng ở gemboss/gemfactory/gemsite). Từ 0.3.11 "Huỷ bỏ lọc…" và "Hủy đơn, gọi chúng tôi bất cứ lúc nào" không bị đọc là chính sách huỷ; "huỷ đăng ký" là bỏ nhận tin (như "Unsubscribe anytime"); `SOURCE.json` ghi `gemtheme_dirty`, và `npm pack` từ chối khi ĐẦU VÀO của gói (`packages/`, gói này trừ `package.json`, `scripts/lib/`) có thay đổi chưa commit hoặc git không trả lời được. Từ 0.3.12 bắt lời hứa hoàn tiền không có chữ "hoàn tiền" liền nhau ("nhận lại toàn bộ tiền", "Hoàn lại 100% tiền", "Trả lại tiền nếu không hài lòng"); lời từ chối ("Không được hoàn lại tiền") và tiền thừa/thối khi thanh toán không bị bắt Từ 0.4.0: `demoOutsidePreview(tsx)` (chữ mẫu DEMO chỉ hiện trong bản xem trước, tức khi `section.demo === true`; plan xuất bản không bao giờ mang cờ đó) và schema `SectionSpec` khai trường `demo` | `packages/gem-sections/src/section-artifacts.ts`, `section-contract.ts` |
11
11
  | `…/settings-schema` | `validateSettingsSchema` | `scripts/lib/settings-schema-contract.mjs` |
12
12
  | `…/gem-framework.css`, `…/gem-runtime.js` | CSS token mặc định + runtime mọi plugin, dựng sẵn. `gem-runtime.js` tự chạy khi tải: đăng ký và lên lịch đủ 23 plugin | build |
@@ -93,6 +93,100 @@ 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.18.11** (19/10/2026): `.gem-stars` nhận sao SVG (gap #45 của GemFactory). Hàng sao rộng đúng bằng các ngôi sao
97
+ (`width: max-content`), và `svg` / `img` trong hàng sao không bị luật nền `max-width: 100%` bóp. Trước đó sao SVG trong
98
+ lớp tô bị co theo bề rộng lớp tô (4,2 vẽ thành năm sao ép), và hàng sao là item bị giãn của grid / flex thì phần trăm tô
99
+ tính theo bề rộng đã giãn (3 trên 5 vẽ thành 5). Hàng sao bằng ký tự ★ trong một flex row (thẻ sản phẩm featured) không đổi.
100
+
101
+ - **0.18.10** (19/10/2026): section-gate (`fabricatedProofInSource`, `VERIFIED_BY_DEFAULT`) bắt cờ "đã xác minh"
102
+ (verified, isVerified, verified_buyer…) đọc theo kiểu thiếu thì coi là đúng: `!== false`, `?? true`, `|| true`,
103
+ `=== false ? null : <badge>`, `{ verified = true }`, kể cả xuống dòng. Huy hiệu "Verified Buyer" chỉ được hiện khi dữ liệu
104
+ nói `verified === true` (star-card-grid đã gắn nó cho mọi review không mang cờ).
105
+ - **0.18.9** (19/10/2026): `gemPlanFormSet(plan)`: bộ form mà MỌI trình vẽ dùng cho một plan. `tokens.form` của plan nếu
106
+ có; `tokens.form: null` khi người bán chọn "None" (không bộ nào); thiếu khoá (plan viết trước khi có bộ form) thì bộ mà
107
+ tính cách của nó suy ra (`deriveFormSetId`). `renderGemPreviewDocument` dùng nó cho cả tờ token lẫn `<body>`, như
108
+ storefront. Schema `tokens.form` nhận `null`. Giá trị lưu không phải một bộ (mảng, object rỗng) được đọc như thiếu khoá.
109
+ - **0.18.8** (19/10/2026): `deriveFormSetId` (và `GEM_SOFT_FIELD_FAMILIES`, `GemFormCharacter`): bộ form mà tính cách của
110
+ store suy ra (bo góc + kiểu nút + họ), chuyển từ `theme-presets` sang đây. Planner vẫn ghi nó vào mọi plan mới; storefront
111
+ dùng chính hàm này cho plan viết trước khi có bộ form (không có `tokens.form`), để mọi store có một bộ form thay vì mỗi
112
+ field một kiểu. `deriveStylePackFormSetId` của `theme-presets` giữ tên, gọi sang hàm này.
113
+ - **0.18.7** (19/10/2026): `GEM_FORM_VAR_SCOPE` khai biến của bộ form trên cả `output`. Gói này không có luật nào tô
114
+ `output` bằng biến của bộ form, nên tự nó không đổi gì trên trang. Nó cần cho gemsite, nơi con số giữa −/+ của ô số
115
+ lượng (giỏ và trang sản phẩm, đều là `<output>`) tô `--gem-input-bg` như hai nút: ngoài phạm vi, con số lấy giá trị đã
116
+ tính ở cấp trang, nên trong một băng có bảng màu riêng hai nút tối mà ô số vẫn sáng dưới chữ trắng của băng. Có hiệu
117
+ lực khi gemsite chép bản này (`lib/form-set.ts` và khối mặc định trong `globals.css`).
118
+ - **0.18.6** (16/10/2026): `./settings-schema` (hợp đồng schema của section): `color_scheme` phải là select trên các bảng
119
+ màu store vẽ được (`RENDERED_COLOR_SCHEMES`, option chuẩn `COLOR_SCHEME_OPTIONS`); `range` phải có `min`/`max`; select
120
+ không option là lỗi. `normalizeSettingsSchema` đổi mọi cách viết cũ của "scheme" (type `color`, `color_scheme`,
121
+ `setting`, `string`) thành select đó. 34/51 dòng `color_scheme` trong thư viện từng là ô chọn màu hoặc ô gõ chữ mà
122
+ store bỏ qua.
123
+
124
+ - **0.18.5** (15/10/2026): Luật nền của thẻ (`data-gem-card`) chỉ khớp `outline|shadow|filled`, luật clip của divider chỉ
125
+ khớp các hình có mask. Trước đó chúng khớp mọi giá trị, kể cả `"none"` (giá trị tắt mà các cửa ghi chuyển nguyên chữ ra
126
+ thuộc tính): hero lưu `card: "none"` từng mang hộp thẻ (đệm 32px, bo góc, ảnh bị bóp). Danh sách trắng viết trong
127
+ `:where(…)` nên độ đặc hiệu giữ nguyên.
128
+
129
+ - **0.18.4** (15/10/2026): Lưới sản phẩm của trang collection (`product-card-grid.quick-view-grid`) cách nhau 8px trên
130
+ điện thoại (trước 4px), như featured collection và hai rail sản phẩm: thẻ giờ là thẻ Dawn phẳng, không đệm riêng.
131
+ Đi cùng section-library 1.42.9 (`sizes` ảnh đo lại cho thẻ phẳng).
132
+
133
+ - **0.18.3** (15/10/2026): Lưới sản phẩm kiểu Dawn (4 cột, 2 cột trên điện thoại, khe 8px) và khung ảnh vuông
134
+ của thẻ áp thêm cho hai rail `recommended-products.product-rail` và `cart-recommendations.product-rail`, vì
135
+ hai section này giờ vẽ `.gem-product-card` (section-library 1.42.7). Không đổi section nào khác.
136
+
137
+ - **0.18.2** (14/10/2026): Checkbox `divider_flip` nhận thêm giá trị lưu `1` và `"1"`, theo cùng
138
+ hợp đồng với checkbox của renderer. `false`, chuỗi false/off/0, giá trị vắng hoặc sai vẫn không flip.
139
+ Không đổi hình học cạnh, CSS, edition hoặc phiên bản section library.
140
+
141
+ - **0.18.1** (14/10/2026): Trên document có divider native, nội dung của section parallax bên cạnh
142
+ cũng được xếp trên cạnh SVG. Chỉ bỏ stacking isolation; clipping nền parallax vẫn giữ nguyên.
143
+ Document không có divider native giữ hành vi cũ kể cả khi stylesheet còn được giữ sau navigation.
144
+
145
+ - **0.18.0** (14/10/2026): Thêm API native cho cạnh section element: `gemSectionDividers`,
146
+ `gemSectionDividerStyle`, `gemSectionDividerResponsiveStyle`, `GEM_ELEMENT_SECTION_DIVIDER_CSS` và
147
+ `GEM_SECTION_DIVIDER_PATHS`. Dùng lại năm shape `wave`, `slope`, `spikes`, `arch`, `blob`; chiều cao 0–320px,
148
+ màu/token và flip được kiểm tra trước khi render. Chiều cao có thể khác ở mobile/tablet. CSS mới chỉ dùng khi
149
+ consumer render lớp SVG native; CSS primitive và divider section-shell cũ giữ nguyên. Cần nâng renderer/editor
150
+ tương ứng trước khi coi các control mới là hoạt động; bump source không phải bằng chứng đã deploy consumer.
151
+
152
+ - **0.17.1** (14/10/2026): bộ đọc schema (`settings-schema.js`) nhận kiểu `datetime`: một thời điểm kèm offset, ISO 8601
153
+ (`2026-12-31T23:59:00+07:00`). Countdown dùng nó cho `target` (gemtheme #776). `requires` nhận thêm `{key, in: [...]}`
154
+ (hiện khi sibling là một trong vài giá trị) và báo lỗi vòng gate (A chờ B, B chờ A). Không đổi CSS hay runtime.
155
+ - **0.17.0** (12/10/2026): **edition 3**, trang collection theo bậc của Dawn. Đo trên theme "Start from scratch" ở 768:
156
+ - trước: bốn thẻ 170px đóng hộp quanh ảnh 120px, không badge Sale / Sold out, tiêu đề ở x=0 trên dải màu nhấn rồi mờ xám;
157
+ - edition 3: thẻ là ảnh + chữ; badge hiện khi section bật `show_badges`; số cột điện thoại (`columns_mobile`) giữ tới 989px;
158
+ tiêu đề compact-collection nằm trên trang, thẳng mép lưới, màu chữ của theme.
159
+ - Băng compact-collection có ẢNH collection (`.has-media`, ảnh là `.gem-hero__bg`) giữ màn phủ và chữ trắng; luật tiêu đề chỉ
160
+ áp khi không có ảnh (review #764: chữ tối trên ảnh còn khoảng 1:1).
161
+ - Theme morph giữ thẻ có hộp: profile morph nhắc lại hộp từ token thẻ, cao hơn một class, nên knob thẻ vẫn sống ở mọi edition.
162
+
163
+ Luật nằm ở trường MỚI `elements` của registry (`EDITION_3_ELEMENTS`), không nằm cạnh luật Dawn-parity: sections.css không
164
+ được nhắc `data-gem-edition` (`editions.test.ts`). `gemFrameworkEditionElementCss()` đóng primitives.css ngay TRƯỚC biến
165
+ của registry, nên mỗi luật thắng bằng selector của chính nó (thêm một class hay attribute), không bằng vị trí. Store đang ở
166
+ edition 1/2 không đổi; store mới nhận edition mới nhất. Chuỗi: gemsite nâng gói + sync (khối `primitives.css from
167
+ :where([data-gem-edition='2'])` dời mốc: BREAKS; `lib/framework-edition.test.ts` nhận luật phần tử; cổng `collection:grid`).
168
+ gemboss: CI đọc `main` của gemtheme, nên test edition (`framework-edition-setting.test.ts`, `the-edition-door-reaches-the-plan`)
169
+ phải hết ghim "edition 2 là mới nhất" và merge TRƯỚC; rồi nâng vendor.
170
+ - **0.16.1** (12/10/2026): SỬA cổng slot-contract (`slotsReadBy`, `…/section-gate`), gap #42 của GemFactory + review #758. Tên túi
171
+ slot chỉ tính khi đứng ĐẦU một biểu thức: không sau chữ, `$`, `-`, `.`, `/` hay dấu nháy. Nên id `…reward-slots.v1`,
172
+ `delivery-picker.slots.v1`, `gift.timeslots.v1`, chuỗi `"./slots.ts"` và `item.content.title` (tên alias làm thuộc tính của thứ
173
+ khác) không còn sinh slot giả. `content_slots` là tên duy nhất được đứng sau `.` (`section.content_slots.heading` vẫn là lượt
174
+ đọc). Thêm: `slots?.x`, `slots?.["x"]`, alias có khai kiểu (`const c: T = …`) và alias qua `section?.content_slots` /
175
+ `props.section.content_slots`. Đánh đổi có chủ ý: phép trừ không cách `total -slots.count` không còn tính. Script
176
+ `audit-slot-key-drift.mjs` dùng chung `slotsReadBy` (chạy bằng `npx tsx`). Thư viện giữ baseline (57/91 sạch). Đối chứng âm:
177
+ bỏ phần lookbehind mới thì 2 ca đỏ. Chuỗi merge: GemFactory nâng lên 0.16.1 thì gỡ (hoặc hạ) cảnh báo `variant_name_slots_trap`,
178
+ vì cổng không còn đọc nhầm.
179
+ - **0.16.0** (12/10/2026): CHỈ THÊM. **Giá thẻ sản phẩm**: `…/sections` (và `@gemtheme/gem-sections`) thêm `cardPrice(product,
180
+ fromLabel?)`, MỘT luật giá cho mọi section thẻ sản phẩm gọi nó. `product-card-grid` quick-view-grid và featured giữ hai bản riêng, sửa
181
+ một bản thì bản kia giữ lỗi, nên cùng một sản phẩm ra hai giá ở hai lưới (gemtheme #754). Trả về:
182
+ - `text` (dòng giá) và `from`: nhiều giá thì "From" + giá THẤP NHẤT (min của priceRange, không thì biến thể rẻ nhất), như Dawn;
183
+ - `compareAt` (có ⇔ có Sale): biến thể MUA ĐƯỢC ở giá đang hiện đang giảm, hoặc giá gạch của chính người bán khi so được là lớn hơn;
184
+ - `soldOut`: cờ curated, `availableForSale` của sản phẩm, hoặc mọi biến thể hết;
185
+ - `variant`: biến thể thêm giỏ được (mua được, có id, đúng giá đang hiện);
186
+ - `amount`: số cho `data-gem-product-price`.
187
+
188
+ Section-library 1.39.6 import nó: GemFactory đồng bộ hai section `product-card-grid` đó khi đã lên 0.16.0, không thì
189
+ `cardPrice is not a function`.
96
190
  - **0.15.0** (11/10/2026): CHỈ THÊM, store đang chạy không đổi pixel nào (chưa trang nào mang các lớp mới). Gap #39: **nhấn
97
191
  trong câu**. Chữ advertorial sống nhờ cách nhấn (kho GemFactory `research/advertorial-blocks-2026-10-10`: souleora 84 cụm đậm,
98
192
  5 nghiêng, 4 đổi màu, 1 gạch chân trong 152 đoạn; 28pilates chốt thẻ "đừng mua" bằng câu đỏ, tô màu nhấn cụm kết quả trong
@@ -116,8 +210,18 @@ Từ 0.8.2 mỗi module có `.d.ts` cạnh nó (`mount(root?: ParentNode | null,
116
210
  trong manifest (nhóm content), nên section vẽ chúng không bị `unknown_gem_class`.
117
211
  - Người dùng: gemboss phải thêm các tên mới vào requireShim của bản xem trước section; gemsite chép `inline-marks.ts`
118
212
  byte-identical (cặp kiểm của `check-elements-copied-from-gemtheme`) rồi vẽ dấu cho element `text` / `heading`.
119
- - **0.14.0** (11/10/2026): CHỈ THÊM, store đang chạy không đổi pixel nào (không section nào của gemtheme, gemsite, gemboss
120
- đọc các tên dưới đây; 30 mục kho GemFactory tự khai thang dùng tên riêng theo mục). Hai gap của bộ interaction "Gọn"
213
+ - **0.14.0** (11/10/2026): thêm thang và plugin (không đổi gì sẵn có), trừ hai chỗ dưới. Không section nào của gemtheme hay
214
+ gemboss đọc các tên mới (đo `git grep -nE -e '--gem-motion-(fast|base|slow|distance|distance-lg|stagger|blur)[^a-z-]' -e
215
+ '--gem-ease-(enter|exit)[^a-z-]'`; ĐỪNG dùng `\b`: git của Apple 2.39.5 không hiểu nó và ra 0 im lặng). Kho GemFactory: 209
216
+ mục tự khai thang bằng tên riêng theo mục, đếm bằng cổng `motion_scale_self_declared` ngày 11/10/2026 trên 259 section; 0 mục
217
+ khai lại biến của gói, 0 mục đọc thiếu số dự phòng. *Sửa 11/10/2026, bản đầu ghi "store đang chạy không đổi pixel nào":*
218
+ (a) gemsite `app/globals.css` có 3 luật họ hero-split (`.gem-hero-split__option`, `__option-arrow`, `__playpause`) đọc
219
+ `var(--gem-motion-fast, 0.2s)`. Từ gemsite #854 (thang vào `:root` của store) transition của chúng (hover, nền, viền, và nhấn
220
+ `:active` của nút play) chạy 100ms thay 200ms, và 1ms khi người xem giảm chuyển động hoặc preset `motion_level: "none"`.
221
+ Component phát ba lớp đó (24 hero của gemsite #150) đã xoá ở gemsite #688, nên ba luật là CSS chết: đề xuất xoá.
222
+ (b) Bí danh motion tra theo slug: plan viết `easing_soft` / `ambient_slow` / `Enter` trước đây ra biến không ai đọc, nay ra
223
+ `--gem-ease-soft` / `--gem-ambient-slow` / `--gem-ease-enter` mà luật `[data-gem-ambient]` đọc, tức là có tác dụng. Chưa plan
224
+ nào trong các repo viết kiểu đó. Hai gap của bộ interaction "Gọn"
121
225
  (Lily 08/10/2026) mà kho đang chép tay vào từng mục:
122
226
  (1) **Thang chuyển động (#32).** `createGemTokenCss` luôn phát `--gem-motion-fast` 100ms, `-base` 180ms, `-slow` 260ms,
123
227
  `--gem-ease-enter` / `--gem-ease-exit`, `--gem-motion-distance` 8px, `-distance-lg` 16px, `-stagger` 30ms, `-blur` 8px
package/dist/SOURCE.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "package": "@gemboss/gem-framework",
3
- "version": "0.15.0",
4
- "gemtheme_sha": "c69c7c6e8d2c2dcf1543170990f6a644805c4dd8",
3
+ "version": "0.18.11",
4
+ "gemtheme_sha": "8ba104ab54bc3c5e5e2e837a5a6a83a2d3fd14c4",
5
5
  "gemtheme_dirty": false,
6
- "framework_version": "0.15.0"
6
+ "framework_version": "0.18.11"
7
7
  }
@@ -28,6 +28,13 @@ export interface GemFrameworkEdition {
28
28
  changes: readonly string[];
29
29
  /** The CSS this edition adds, unscoped; gemFrameworkEditionCss scopes it. Empty for edition 1. */
30
30
  css: readonly string[];
31
+ /**
32
+ * ELEMENT rules this edition adds, `:where(&)`-scoped like `css` (gemFrameworkEditionElementCss). They close primitives.css
33
+ * just BEFORE the custom properties above, so they come before sections.css: each must out-rank, by its own selector, the
34
+ * sections.css and storefront rules it changes (a class or an attribute more), never by position. Kept here, not beside
35
+ * those rules, because sections.css must not name data-gem-edition (a new edition would move the storefront's anchors).
36
+ */
37
+ elements?: readonly string[];
31
38
  }
32
39
  export declare const GEM_EDITION_SUPPORT_MONTHS = 6;
33
40
  export declare const GEM_FRAMEWORK_EDITIONS: readonly GemFrameworkEdition[];
@@ -48,6 +55,8 @@ export declare function gemFrameworkEditionSupportedUntil(edition: unknown): str
48
55
  export declare function addEditionSupportMonths(released: string): string;
49
56
  /** `:where([data-gem-edition='N'], [data-gem-edition='N+1'], …)`: the attribute selectors of edition N and every later one. */
50
57
  export declare function gemFrameworkEditionScope(edition: number): string;
58
+ /** Every edition's ELEMENT rules, scoped, oldest first: they close primitives.css just before gemFrameworkEditionCss. */
59
+ export declare function gemFrameworkEditionElementCss(): string;
51
60
  /**
52
61
  * Every edition's CSS, scoped, oldest first. It closes primitives.css (primitives.ts createGemThemeCssParts): the rules set
53
62
  * custom properties on <html>, so their place in the sheet does not decide anything against element rules.
package/dist/editions.js CHANGED
@@ -117,6 +117,14 @@ var GEM_EDITION_SUPPORT_MONTHS = 6;
117
117
  var EDITION_2_CSS = [
118
118
  `:where(&) { ${GEM_HEADING_SIZE_CSS_VARS.filter((decl) => !decl.startsWith("--gem-section-heading-size-mobile") && !decl.startsWith("--gem-hero-heading-size-mobile")).join(" ")} }`
119
119
  ];
120
+ var EDITION_3_ELEMENTS = [
121
+ `:where(&) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }`,
122
+ `:where(&) .gem-product-card-grid--quick-view-grid .gem-product-card__media > .gem-product-card__badges { display: flex; }`,
123
+ `@media (max-width: 989px) { :where(&) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid[data-gem-product-grid] { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); } }`,
124
+ `:where(&) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) { position: relative; inset: auto; z-index: auto; opacity: 1; width: min(var(--gem-layout-page, 1200px), calc(100% - var(--gem-layout-gutter, 32px))); margin: clamp(24px, 5vw, 48px) auto 0; background: none; color: var(--gem-color-text); border-radius: 0; }`,
125
+ `:where(&) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) .gem-hero__heading { color: var(--gem-color-text); }`,
126
+ `:where(&) .gem-section.gem-hero.gem-hero-banner--compact-collection:not(:has(> .gem-hero__bg)) { padding-block-end: 0; }`
127
+ ];
120
128
  var GEM_FRAMEWORK_EDITIONS = [
121
129
  { edition: 1, since: "0.1.0", released: "2026-07-04", changes: [], css: [] },
122
130
  {
@@ -125,6 +133,18 @@ var GEM_FRAMEWORK_EDITIONS = [
125
133
  released: "2026-10-10",
126
134
  changes: ["Section headings and hero titles follow your type scale on tablets and computers. Phones keep their current size."],
127
135
  css: EDITION_2_CSS
136
+ },
137
+ {
138
+ // Edition 3. Collection pages at Dawn's tiers (12/10/2026, measured on a "Start from scratch" theme): EDITION_3_ELEMENTS.
139
+ edition: 3,
140
+ since: "0.17.0",
141
+ released: "2026-10-12",
142
+ changes: [
143
+ "Collection grids: each product photo fills its column, phones and tablets show your mobile column count, and Sale and Sold out badges appear.",
144
+ "The collection title sits on the page, lined up with the products, in your text colour."
145
+ ],
146
+ css: [],
147
+ elements: EDITION_3_ELEMENTS
128
148
  }
129
149
  ];
130
150
  var LATEST_GEM_FRAMEWORK_EDITION = GEM_FRAMEWORK_EDITIONS[GEM_FRAMEWORK_EDITIONS.length - 1].edition;
@@ -146,6 +166,9 @@ function addEditionSupportMonths(released) {
146
166
  function gemFrameworkEditionScope(edition) {
147
167
  return GEM_FRAMEWORK_EDITIONS.filter((e) => e.edition >= edition).map((e) => `[data-gem-edition='${e.edition}']`).join(", ");
148
168
  }
169
+ function gemFrameworkEditionElementCss() {
170
+ return GEM_FRAMEWORK_EDITIONS.flatMap((e) => (e.elements ?? []).map((rule) => rule.replaceAll("&", gemFrameworkEditionScope(e.edition)))).join("\n");
171
+ }
149
172
  function gemFrameworkEditionCss() {
150
173
  return GEM_FRAMEWORK_EDITIONS.flatMap((e) => e.css.map((rule) => rule.replaceAll("&", gemFrameworkEditionScope(e.edition)))).join("\n");
151
174
  }
@@ -155,6 +178,7 @@ export {
155
178
  LATEST_GEM_FRAMEWORK_EDITION,
156
179
  addEditionSupportMonths,
157
180
  gemFrameworkEditionCss,
181
+ gemFrameworkEditionElementCss,
158
182
  gemFrameworkEditionOf,
159
183
  gemFrameworkEditionScope,
160
184
  gemFrameworkEditionSupportedUntil
@@ -586,12 +586,14 @@ a {
586
586
  --gem-stars-value: 5;
587
587
  position: relative;
588
588
  display: inline-block;
589
+ width: max-content;
589
590
  line-height: 1;
590
591
  font-size: var(--gem-text-sm, 0.8125rem);
591
592
  letter-spacing: 0.1em;
592
593
  color: var(--gem-color-border);
593
594
  }
594
595
  .gem-stars__base, .gem-stars__fill { display: block; white-space: nowrap; }
596
+ .gem-stars :is(svg, img) { max-width: none; flex: none; }
595
597
  .gem-stars__fill {
596
598
  position: absolute;
597
599
  inset: 0;
@@ -1142,7 +1144,7 @@ a {
1142
1144
  [data-gem-ambient] { --gem-ambient-amp: 2px; animation-duration: 12s; }
1143
1145
  [data-gem-reveal] { transition: none; opacity: 1; transform: none; }
1144
1146
  }
1145
- :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top], :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom] { position: relative; overflow: clip; overflow-clip-margin: var(--gem-divider-height, 48px); }
1147
+ :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top]:where([data-gem-divider-top='wave'], [data-gem-divider-top='slope'], [data-gem-divider-top='spikes'], [data-gem-divider-top='arch'], [data-gem-divider-top='blob']), :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom]:where([data-gem-divider-bottom='wave'], [data-gem-divider-bottom='slope'], [data-gem-divider-bottom='spikes'], [data-gem-divider-bottom='arch'], [data-gem-divider-bottom='blob']) { position: relative; overflow: clip; overflow-clip-margin: var(--gem-divider-height, 48px); }
1146
1148
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='wave']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='slope']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='spikes']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='arch']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='blob']::after, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='wave']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='slope']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='spikes']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='arch']::before, :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='blob']::before { content: ''; position: absolute; left: 0; right: 0; z-index: 1; pointer-events: none; height: var(--gem-divider-height, 48px); background: var(--gem-section-bg, var(--gem-color-background)); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; }
1147
1149
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom]::after { top: 100%; }
1148
1150
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top]::before { bottom: 100%; transform: scaleY(-1); }
@@ -1156,7 +1158,7 @@ a {
1156
1158
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='arch']::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V2 Q50 12 0 2 Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V2 Q50 12 0 2 Z'/%3E%3C/svg%3E"); }
1157
1159
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-bottom='blob']::after { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); }
1158
1160
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-divider-top='blob']::before { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 0 H100 V4 C85 11 72 0 58 5 C44 10 30 2 16 6 C10 8 5 5 0 6 Z'/%3E%3C/svg%3E"); }
1159
- :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card] :is(.gem-section__inner, [class*='__inner']) { border-radius: var(--gem-radius-md); padding: var(--gem-space-xl); }
1161
+ :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card]:where([data-gem-card='outline'], [data-gem-card='shadow'], [data-gem-card='filled']) :is(.gem-section__inner, [class*='__inner']) { border-radius: var(--gem-radius-md); padding: var(--gem-space-xl); }
1160
1162
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='outline'] :is(.gem-section__inner, [class*='__inner']) { border: 1px solid var(--gem-color-border); }
1161
1163
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='shadow'] :is(.gem-section__inner, [class*='__inner']) { background: var(--gem-color-surface); box-shadow: var(--gem-shadow-md); }
1162
1164
  :is(.gem-section, .gem-hero, .gem-hero-split, .gem-hero-collage, .gem-hero-triptych, .gem-slideshow)[data-gem-card='filled'] :is(.gem-section__inner, [class*='__inner']) { background: color-mix(in srgb, currentColor 6%, transparent); }
@@ -1526,7 +1528,14 @@ a {
1526
1528
  .gem-collection-filter-toolbar__active-filter, .gem-collection-filter-toolbar__clear { min-height: 2.25rem; align-items: center; }
1527
1529
  }
1528
1530
 
1529
- :where([data-gem-edition='2']) { --gem-section-heading-size: calc(var(--gem-h2) * var(--gem-typography-heading-scale, 1)); --gem-hero-heading-size: calc(var(--gem-h1) * var(--gem-typography-heading-scale, 1)); }
1531
+ :where([data-gem-edition='3']) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }
1532
+ :where([data-gem-edition='3']) .gem-product-card-grid--quick-view-grid .gem-product-card__media > .gem-product-card__badges { display: flex; }
1533
+ @media (max-width: 989px) { :where([data-gem-edition='3']) .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid[data-gem-product-grid] { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); } }
1534
+ :where([data-gem-edition='3']) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) { position: relative; inset: auto; z-index: auto; opacity: 1; width: min(var(--gem-layout-page, 1200px), calc(100% - var(--gem-layout-gutter, 32px))); margin: clamp(24px, 5vw, 48px) auto 0; background: none; color: var(--gem-color-text); border-radius: 0; }
1535
+ :where([data-gem-edition='3']) .gem-hero.gem-hero-banner--compact-collection .gem-hero__overlay:not(.has-media) .gem-hero__heading { color: var(--gem-color-text); }
1536
+ :where([data-gem-edition='3']) .gem-section.gem-hero.gem-hero-banner--compact-collection:not(:has(> .gem-hero__bg)) { padding-block-end: 0; }
1537
+
1538
+ :where([data-gem-edition='2'], [data-gem-edition='3']) { --gem-section-heading-size: calc(var(--gem-h2) * var(--gem-typography-heading-scale, 1)); --gem-hero-heading-size: calc(var(--gem-h1) * var(--gem-typography-heading-scale, 1)); }
1530
1539
 
1531
1540
  .gem-popup-shell { padding: 0; }
1532
1541
  .gem-popup-shell__dialog {
@@ -2480,7 +2489,7 @@ main > .gem-section:first-child { padding-top: 0; }
2480
2489
  .gem-card, .gem-product-card { border: 0; border-radius: 0; box-shadow: none; background: transparent; padding: 0; }
2481
2490
  .gem-product-card__media, .gem-media { border: 0; border-radius: 0; box-shadow: none; background: rgb(244 244 244); }
2482
2491
  .gem-product-card__media { display: block; position: relative; width: 100%; height: 0; min-block-size: 0; padding-bottom: 100%; aspect-ratio: auto; overflow: hidden; }
2483
- .gem-featured-collection--editorial-grid .gem-product-card__media, .gem-product-card-grid--quick-view-grid .gem-product-card__media, .gem-main-search .gem-product-card__media, .gem-cart-recommendations--dawn-empty .gem-product-card__media { min-block-size: 0 !important; }
2492
+ .gem-featured-collection--editorial-grid .gem-product-card__media, .gem-product-card-grid--quick-view-grid .gem-product-card__media, .gem-main-search .gem-product-card__media, .gem-cart-recommendations--dawn-empty .gem-product-card__media, .gem-recommended-products--product-rail .gem-product-card__media, .gem-cart-recommendations--product-rail .gem-product-card__media { min-block-size: 0 !important; }
2484
2493
  .gem-product-card__media > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
2485
2494
  .gem-featured-collection .gem-product-card__media > img, .gem-product-card-grid .gem-product-card__media > img { object-fit: cover; }
2486
2495
  .gem-product-card-grid--quick-view-grid .gem-product-card__media { display: block; min-height: 0; }
@@ -2494,7 +2503,7 @@ main > .gem-section:first-child { padding-top: 0; }
2494
2503
  .gem-cart-recommendations--dawn-empty .gem-product-card__quick-actions { display: none; }
2495
2504
  .gem-featured-collection--editorial-grid > .gem-section__inner > .gem-section__heading, .gem-featured-collection--editorial-grid > .gem-section__inner > .gem-section__body { display: none; }
2496
2505
  .gem-featured-collection--editorial-grid { padding-block: 28px 36px; }
2497
- .gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-columns, 4), minmax(0, 1fr)); gap: 8px; }
2506
+ .gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid, .gem-recommended-products--product-rail .gem-section__grid, .gem-cart-recommendations--product-rail .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-columns, 4), minmax(0, 1fr)); gap: 8px; }
2498
2507
  .gem-product-card-grid--quick-view-grid { padding-top: 0; }
2499
2508
  .gem-product-card-grid--quick-view-grid .gem-section__heading:empty { display: none; }
2500
2509
  .gem-product-card-grid--quick-view-grid .gem-price:not(.gem-price--on-sale) .gem-price__compare { display: none; }
@@ -2648,13 +2657,13 @@ main > .gem-section:first-child { padding-top: 0; }
2648
2657
  .gem-hero-banner--compact-collection { padding-block: 25px; }
2649
2658
  .gem-hero-banner--compact-collection .gem-section__heading { font-size: 30px; line-height: 39px; }
2650
2659
  .gem-product-card-grid--quick-view-grid > .gem-section__inner, .gem-main-search > .gem-section__inner { width: calc(100% - 30px); }
2651
- .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid { gap: 4px !important; }
2660
+ .gem-product-card-grid--quick-view-grid .gem-product-card-grid__grid { gap: 8px !important; }
2652
2661
  .gem-product-card__content { padding-top: 8px; gap: 4px; }
2653
2662
  .gem-product-card__title { font-size: 15px; line-height: 24px; }
2654
2663
  .gem-product-card__price, .gem-product-card__price .gem-price { font-size: 16px; line-height: 24px; }
2655
2664
  .gem-section__heading { font-size: var(--gem-section-heading-size-mobile, var(--gem-heading-phone-cap)); }
2656
2665
  .gem-hero-banner h1, .gem-hero-banner h2 { font-size: var(--gem-hero-heading-size-mobile, var(--gem-heading-phone-cap)); }
2657
- .gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); }
2666
+ .gem-featured-collection--editorial-grid .gem-section__grid, .gem-product-card-grid--quick-view-grid .gem-section__grid, .gem-main-search .gem-section__grid, .gem-recommended-products--product-rail .gem-section__grid, .gem-cart-recommendations--product-rail .gem-section__grid { grid-template-columns: repeat(var(--gem-product-card-grid-mobile-columns, 2), minmax(0, 1fr)); }
2658
2667
  .gem-product-main[data-gem-product-main-layout='dawn-main'] > .gem-section__inner { width: calc(100% - 30px); }
2659
2668
  .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-media-gallery__frame { display: flex; gap: 0; overflow-x: auto; scroll-snap-type: x mandatory; }
2660
2669
  .gem-product-main[data-gem-product-main-layout='dawn-main'] .gem-product-media-gallery__item { flex: 0 0 100%; scroll-snap-align: start; aspect-ratio: 1 / 1; }