@syntrologie/adapt-product 2.55.0 → 2.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/bind/controller.d.ts +22 -1
  2. package/dist/bind/controller.d.ts.map +1 -1
  3. package/dist/cdn.js +2 -2
  4. package/dist/{chunk-QXR5X22R.js → chunk-QNZXBFP5.js} +1021 -1209
  5. package/dist/chunk-QNZXBFP5.js.map +7 -0
  6. package/dist/chunk-RUE5UUEN.js +5206 -0
  7. package/dist/chunk-RUE5UUEN.js.map +7 -0
  8. package/dist/decision/card.d.ts +4 -0
  9. package/dist/decision/card.d.ts.map +1 -1
  10. package/dist/platformAdapter.d.ts +31 -0
  11. package/dist/platformAdapter.d.ts.map +1 -1
  12. package/dist/runtime.d.ts +9 -0
  13. package/dist/runtime.d.ts.map +1 -1
  14. package/dist/runtime.js +4 -2
  15. package/dist/sanitizer.d.ts +4 -0
  16. package/dist/sanitizer.d.ts.map +1 -1
  17. package/dist/schema.d.ts +32 -0
  18. package/dist/schema.d.ts.map +1 -1
  19. package/dist/schema.js +1 -1
  20. package/dist/storefront-urls.d.ts +9 -0
  21. package/dist/storefront-urls.d.ts.map +1 -0
  22. package/dist/widgets/OtherProductTileLit.d.ts +20 -2
  23. package/dist/widgets/OtherProductTileLit.d.ts.map +1 -1
  24. package/dist/widgets/PdpChartBarStackedLit.d.ts.map +1 -1
  25. package/dist/widgets/PdpChartLit.d.ts.map +1 -1
  26. package/dist/widgets/PdpChartMetricPullLit.d.ts.map +1 -1
  27. package/dist/widgets/PdpOtherProductsCarouselLit.d.ts.map +1 -1
  28. package/dist/widgets/PdpOtherProductsGridLit.d.ts.map +1 -1
  29. package/dist/widgets/PdpPeerFeedCarouselLit.d.ts.map +1 -1
  30. package/dist/widgets/PdpPeerFeedLit.d.ts.map +1 -1
  31. package/dist/widgets/PdpPeerFeedMasonryLit.d.ts.map +1 -1
  32. package/dist/widgets/PdpQaAccordionLit.d.ts.map +1 -1
  33. package/dist/widgets/PdpQaLit.d.ts.map +1 -1
  34. package/dist/widgets/PdpQaSideBySideLit.d.ts.map +1 -1
  35. package/dist/widgets/PdpRegionalFullBleedLit.d.ts.map +1 -1
  36. package/dist/widgets/PdpRegionalInlineNoteLit.d.ts.map +1 -1
  37. package/dist/widgets/PdpRegionalLit.d.ts.map +1 -1
  38. package/dist/widgets/PdpRibbonLit.d.ts.map +1 -1
  39. package/dist/widgets/PdpSectionHeaderLit.d.ts.map +1 -1
  40. package/dist/widgets/PdpSlotLit.d.ts.map +1 -1
  41. package/dist/widgets/PdpTrendingNewsHeroLit.d.ts.map +1 -1
  42. package/dist/widgets/PdpTrendingNewsLit.d.ts.map +1 -1
  43. package/dist/widgets/PdpTrendingNewsMagazineLit.d.ts.map +1 -1
  44. package/dist/widgets/ProductCardLit.d.ts +3 -8
  45. package/dist/widgets/ProductCardLit.d.ts.map +1 -1
  46. package/dist/widgets/ProductComparisonLit.d.ts +1 -5
  47. package/dist/widgets/ProductComparisonLit.d.ts.map +1 -1
  48. package/dist/widgets/ProductDetailsLit.d.ts +31 -0
  49. package/dist/widgets/ProductDetailsLit.d.ts.map +1 -0
  50. package/dist/widgets/ProductGridLit.d.ts +2 -4
  51. package/dist/widgets/ProductGridLit.d.ts.map +1 -1
  52. package/dist/widgets/ProductHeroLit.d.ts +2 -4
  53. package/dist/widgets/ProductHeroLit.d.ts.map +1 -1
  54. package/dist/widgets/ProductRecoCarouselLit.d.ts +7 -0
  55. package/dist/widgets/ProductRecoCarouselLit.d.ts.map +1 -1
  56. package/dist/widgets/SyntroPdpLit.d.ts +12 -3
  57. package/dist/widgets/SyntroPdpLit.d.ts.map +1 -1
  58. package/dist/widgets/VariantPanelLit.d.ts +9 -0
  59. package/dist/widgets/VariantPanelLit.d.ts.map +1 -1
  60. package/dist/widgets/presentationContext.d.ts +11 -0
  61. package/dist/widgets/presentationContext.d.ts.map +1 -0
  62. package/package.json +4 -7
  63. package/dist/chunk-D2W6UYLT.js +0 -370
  64. package/dist/chunk-D2W6UYLT.js.map +0 -7
  65. package/dist/chunk-QXR5X22R.js.map +0 -7
@@ -1,546 +1,23 @@
1
1
  import {
2
2
  VISIBLE_FACT_KEYS,
3
+ ViewportClassController,
3
4
  __privateAdd,
4
5
  __privateGet,
5
6
  __privateMethod,
6
7
  __privateSet,
7
8
  cardSchema,
8
9
  comparisonSchema,
10
+ dispatchDiveDeeper,
11
+ external_exports,
12
+ fetchLivePrice,
9
13
  getProductPlatformAdapter,
10
14
  gridSchema,
11
15
  heroSchema,
12
16
  recoSchema,
13
- takeoverPlanSchema
14
- } from "./chunk-D2W6UYLT.js";
15
-
16
- // ../../../node_modules/@lit/context/lib/create-context.js
17
- function n(n2) {
18
- return n2;
19
- }
20
-
21
- // ../../sdk-contracts/dist/canvas-context.js
22
- var canvasRuntimeContext = n("syntrologie:canvas-runtime");
23
-
24
- // ../../sdk-contracts/dist/detector-events.js
25
- var DETECTOR_EVENT_NAMES = [
26
- "ui.hover",
27
- "ui.idle",
28
- "ui.hesitate",
29
- "ui.rage_click",
30
- "ui.scroll_thrash",
31
- "ui.focus_bounce"
32
- ];
33
- var CANONICAL_BUS_EVENT_NAMES = [
34
- ...DETECTOR_EVENT_NAMES,
35
- "nav.section_viewed",
36
- "nav.scroll_depth"
37
- ];
38
-
39
- // ../../sdk-contracts/dist/dive-deeper.js
40
- var DIVE_DEEPER_EVENT = "syntro:chat:dive-deeper";
41
- function dispatchDiveDeeper(detail) {
42
- if (typeof window === "undefined")
43
- return;
44
- window.dispatchEvent(new CustomEvent(DIVE_DEEPER_EVENT, { detail }));
45
- }
46
-
47
- // ../../sdk-contracts/dist/mount-plumbing.js
48
- var MOUNT_PLUMBING_KEYS = ["instanceId", "runtime", "tileId"];
49
- function stripMountPlumbing(config) {
50
- if (!config || typeof config !== "object") {
51
- return {};
52
- }
53
- const out = { ...config };
54
- for (const key of MOUNT_PLUMBING_KEYS) {
55
- delete out[key];
56
- }
57
- return out;
58
- }
59
-
60
- // ../../sdk-contracts/dist/routes.js
61
- var RESERVED_BYTES = /* @__PURE__ */ new Set([
62
- 33,
63
- // !
64
- 35,
65
- // #
66
- 36,
67
- // $
68
- 38,
69
- // &
70
- 39,
71
- // '
72
- 40,
73
- // (
74
- 41,
75
- // )
76
- 42,
77
- // *
78
- 43,
79
- // +
80
- 44,
81
- // ,
82
- 47,
83
- // /
84
- 58,
85
- // :
86
- 59,
87
- // ;
88
- 61,
89
- // =
90
- 63,
91
- // ?
92
- 64,
93
- // @
94
- 91,
95
- // [
96
- 93
97
- // ]
98
- ]);
99
- var utf8Decoder = new TextDecoder("utf-8", { fatal: false });
100
- function decodeUnreservedOnly(input) {
101
- let out = "";
102
- let pending2 = [];
103
- const flushPending = () => {
104
- if (pending2.length === 0)
105
- return;
106
- const bytes = new Uint8Array(pending2);
107
- out += utf8Decoder.decode(bytes);
108
- pending2 = [];
109
- };
110
- let i2 = 0;
111
- while (i2 < input.length) {
112
- const ch = input[i2];
113
- if (ch === "%" && i2 + 2 < input.length && isHex(input[i2 + 1]) && isHex(input[i2 + 2])) {
114
- const byte = parseInt(input.slice(i2 + 1, i2 + 3), 16);
115
- if (RESERVED_BYTES.has(byte)) {
116
- flushPending();
117
- out += `%${input.slice(i2 + 1, i2 + 3).toUpperCase()}`;
118
- i2 += 3;
119
- } else {
120
- pending2.push(byte);
121
- i2 += 3;
122
- }
123
- } else {
124
- flushPending();
125
- out += ch;
126
- i2 += 1;
127
- }
128
- }
129
- flushPending();
130
- return out;
131
- }
132
- function isHex(c2) {
133
- return c2 >= "0" && c2 <= "9" || c2 >= "a" && c2 <= "f" || c2 >= "A" && c2 <= "F";
134
- }
135
- function stripQueryAndHash(s4) {
136
- const q = s4.indexOf("?");
137
- if (q !== -1)
138
- s4 = s4.slice(0, q);
139
- const h = s4.indexOf("#");
140
- if (h !== -1)
141
- s4 = s4.slice(0, h);
142
- return s4;
143
- }
144
- function normalizeRoute(path) {
145
- if (typeof path !== "string" || path.length === 0) {
146
- throw new TypeError("normalizeRoute: input must be a non-empty string");
147
- }
148
- if (!path.startsWith("/")) {
149
- throw new TypeError(`normalizeRoute: input must be absolute (start with '/'); got ${JSON.stringify(path)}`);
150
- }
151
- let s4 = stripQueryAndHash(path);
152
- s4 = decodeUnreservedOnly(s4);
153
- s4 = s4.replace(/\/+/g, "/");
154
- if (s4.length > 1 && s4.endsWith("/"))
155
- s4 = s4.slice(0, -1);
156
- return s4;
157
- }
158
-
159
- // ../../sdk-contracts/dist/schemas.js
160
- import { z } from "zod";
161
- var NO_CSS_BREAKOUT_PATTERN = /^[^{}]*$/;
162
- var AnchorIdZ = z.object({
163
- selector: z.string().regex(NO_CSS_BREAKOUT_PATTERN, {
164
- message: 'selector must not contain "{" or "}" \u2014 not valid CSS selector syntax, and content:hideBySelector injects this value directly into a <style> element where these characters would break out of the generated rule.'
165
- }).describe("CSS selector for the target element"),
166
- route: z.union([z.string(), z.array(z.string())]).superRefine((value, ctx) => {
167
- for (const route of Array.isArray(value) ? value : [value]) {
168
- let canonical;
169
- try {
170
- canonical = normalizeRoute(route);
171
- } catch (err) {
172
- ctx.addIssue({
173
- code: z.ZodIssueCode.custom,
174
- message: `route must be an absolute path starting with "/" (got ${JSON.stringify(route)}): ${err instanceof Error ? err.message : String(err)}`
175
- });
176
- continue;
177
- }
178
- if (canonical !== route) {
179
- ctx.addIssue({
180
- code: z.ZodIssueCode.custom,
181
- message: `route ${JSON.stringify(route)} is not canonical \u2014 use ${JSON.stringify(canonical)} (this must match what the backend's RouteCanonicalityCheck accepts)`
182
- });
183
- }
184
- }
185
- }).describe("URL path(s) where this element exists")
186
- }).strict().describe("DOM element target. selector = CSS selector, route = URL path(s) where the element exists.");
187
- var AuthoringFieldsZ = {
188
- id: z.string().optional().describe('Stable action identifier (e.g. "act_3db6a14d2ab0").'),
189
- title: z.string().max(200).optional().describe("Authoring-only: short label shown on the action plan dashboard. Stripped before serving to the runtime SDK."),
190
- description: z.string().max(1e3).optional().describe("Authoring-only: one-sentence explanation of what this action does and why. Stripped before serving to the runtime SDK."),
191
- validation: z.array(z.string().max(500)).max(10).optional().describe("Authoring-only: ordered steps a reviewer can follow to trigger this action and visually confirm it works. Each entry is one step. Stripped before serving to the runtime SDK.")
192
- };
193
- var COUNTABLE_EVENTS = [
194
- // User interactions (from PostHog autocapture normalization)
195
- "ui.click",
196
- "ui.scroll",
197
- "ui.input",
198
- "ui.change",
199
- "ui.submit",
200
- // Behavioral detectors (from event-processor)
201
- "ui.hover",
202
- "ui.idle",
203
- "ui.scroll_thrash",
204
- "ui.focus_bounce",
205
- "ui.hesitate",
206
- "ui.rage_click",
207
- // Navigation
208
- "nav.page_view",
209
- "nav.page_leave"
210
- ];
211
- var CountableEventZ = z.enum(COUNTABLE_EVENTS).describe("Event name to count. ui.* = user interactions and behavioral detectors (hesitate, rage_click, scroll_thrash, focus_bounce, idle, hover); nav.* = page navigation.");
212
- var SESSION_METRIC_KEYS = ["time_on_page", "page_views", "scroll_depth"];
213
- var SessionMetricKeyZ = z.enum(SESSION_METRIC_KEYS).describe("Session metric key. time_on_page = seconds on current page, page_views = pages visited this session, scroll_depth = 0-100 percentage.");
214
- var PageUrlConditionZ = z.object({
215
- type: z.literal("page_url"),
216
- url: z.string().describe('URL path to match (e.g. "/pricing", "/dashboard")')
217
- }).describe('Fires when the current page URL matches. Use for page-specific actions. Example: {"type": "page_url", "url": "/pricing"}');
218
- var RouteConditionZ = z.object({
219
- type: z.literal("route"),
220
- routeId: z.string().describe("Named route ID from the route filter")
221
- }).describe("Fires when the current route matches a named route ID.");
222
- var AnchorVisibleConditionZ = z.object({
223
- type: z.literal("anchor_visible"),
224
- anchorId: z.string().describe("CSS selector of the anchor element"),
225
- state: z.enum(["visible", "present", "absent"]).describe('"visible" = in viewport, "present" = in DOM, "absent" = not in DOM')
226
- }).describe(`Fires based on a DOM element's visibility state. Example: {"type": "anchor_visible", "anchorId": "#cta-button", "state": "visible"}`);
227
- var EventOccurredConditionZ = z.object({
228
- type: z.literal("event_occurred"),
229
- eventName: z.string().describe('Event name (e.g. "ui.click", "$pageview")'),
230
- withinMs: z.number().optional().describe("Time window in ms. Omit = any time this session.")
231
- }).describe('Fires when a specific event has occurred during this session. Example: {"type": "event_occurred", "eventName": "ui.click", "withinMs": 5000}');
232
- var StateEqualsConditionZ = z.object({
233
- type: z.literal("state_equals"),
234
- key: z.string().describe("Key in the SDK persistent state store (localStorage). Only valid for keys the host app explicitly sets via syntro.state.set()."),
235
- value: z.unknown().describe("Expected value to match against")
236
- }).describe("Checks the SDK persistent state store (localStorage). ONLY for host-app state set via syntro.state.set() \u2014 NOT for user attributes like region, device, or UTM params (those are handled by segment targeting). Do NOT use this for targeting. If you do not know the valid state keys, do not use this condition type.");
237
- var ViewportConditionZ = z.object({
238
- type: z.literal("viewport"),
239
- minWidth: z.number().optional().describe("Minimum viewport width in pixels"),
240
- maxWidth: z.number().optional().describe("Maximum viewport width in pixels"),
241
- minHeight: z.number().optional().describe("Minimum viewport height in pixels"),
242
- maxHeight: z.number().optional().describe("Maximum viewport height in pixels")
243
- }).describe('Fires based on viewport (screen) size. Use for responsive behavior. Example: {"type": "viewport", "minWidth": 768} \u2014 fires on tablet and larger.');
244
- var SessionMetricConditionZ = z.object({
245
- type: z.literal("session_metric"),
246
- key: SessionMetricKeyZ,
247
- operator: z.enum(["gte", "lte", "eq", "gt", "lt"]),
248
- threshold: z.number().describe("Numeric threshold to compare against")
249
- }).describe('Fires when a session metric crosses a threshold. Valid keys: "time_on_page" (seconds), "page_views" (count), "scroll_depth" (0-100). Example: {"type": "session_metric", "key": "time_on_page", "operator": "gte", "threshold": 30}');
250
- var DismissedConditionZ = z.object({
251
- type: z.literal("dismissed"),
252
- key: z.string().describe("Dismissal key (usually a tile or action ID)"),
253
- inverted: z.boolean().optional().describe("When true, fires if NOT dismissed (default behavior)")
254
- }).describe("Checks if an item has been dismissed by the user. Use with inverted: true to show only if not dismissed.");
255
- var CooldownActiveConditionZ = z.object({
256
- type: z.literal("cooldown_active"),
257
- key: z.string().describe("Cooldown key"),
258
- inverted: z.boolean().optional().describe("When true, fires if cooldown is NOT active")
259
- }).describe("Checks if a cooldown timer is currently active. Use to prevent showing the same intervention too frequently.");
260
- var FrequencyLimitConditionZ = z.object({
261
- type: z.literal("frequency_limit"),
262
- key: z.string().describe("Frequency counter key"),
263
- limit: z.number().describe("Maximum allowed count"),
264
- inverted: z.boolean().optional().describe("When true, fires if limit NOT reached")
265
- }).describe("Checks if a frequency limit has been reached. Use to cap how many times an action fires per session.");
266
- var MatchOpZ = z.object({
267
- equals: z.union([z.string(), z.number(), z.boolean()]).optional(),
268
- contains: z.string().optional()
269
- }).refine((operator) => Number(operator.equals !== void 0) + Number(operator.contains !== void 0) === 1, {
270
- message: "Exactly one of equals or contains must be specified."
271
- }).describe("Match operator for counter filters. Exactly one of equals or contains must be specified.");
272
- var CounterDefZ = z.object({
273
- events: z.array(CountableEventZ).min(1).describe("Event names to count. Use values from the countable events enum."),
274
- match: z.record(z.string(), MatchOpZ).optional().describe("Property filters. Keys are event prop names or element-chain fields (tag_name, $el_text, attr__*). All entries AND together.")
275
- }).describe("Defines what events to count. Registered as an accumulator predicate at config-load time.");
276
- var EventCountConditionZ = z.object({
277
- type: z.literal("event_count"),
278
- key: z.string().describe("Unique key for this counter (used for accumulator registration)"),
279
- operator: z.enum(["gte", "lte", "eq", "gt", "lt"]),
280
- count: z.number().int().min(0).describe("Target count threshold"),
281
- withinMs: z.number().positive().optional().describe("Time window in ms. Omit = count across entire session."),
282
- counter: CounterDefZ.optional().describe("Inline counter definition. Defines what events to count.")
283
- }).describe('Fires when accumulated event count crosses a threshold. Most powerful trigger type. Example: {"type": "event_count", "key": "pricing-clicks", "operator": "gte", "count": 3, "counter": {"events": ["ui.click"], "match": {"attr__data-cta": {"contains": "pricing"}}}}');
284
- var ConditionZ = z.discriminatedUnion("type", [
285
- PageUrlConditionZ,
286
- RouteConditionZ,
287
- AnchorVisibleConditionZ,
288
- EventOccurredConditionZ,
289
- StateEqualsConditionZ,
290
- ViewportConditionZ,
291
- SessionMetricConditionZ,
292
- DismissedConditionZ,
293
- CooldownActiveConditionZ,
294
- FrequencyLimitConditionZ,
295
- EventCountConditionZ
296
- ]);
297
- var RuleZ = z.object({
298
- conditions: z.array(ConditionZ).describe("Array of conditions \u2014 ALL must match (AND logic) for this rule to fire."),
299
- value: z.unknown().describe("Value returned when all conditions match. For triggerWhen: true = fire the action.")
300
- }).describe("A single rule. ALL conditions must match (AND logic). Rules in a strategy are evaluated top-to-bottom \u2014 first rule where all conditions match wins and returns its value.");
301
- var RuleStrategyZ = z.object({
302
- type: z.literal("rules"),
303
- rules: z.array(RuleZ).describe("Ordered list of rules. Evaluated top-to-bottom \u2014 first match wins."),
304
- default: z.unknown().describe("Fallback value when no rule matches. For triggerWhen: false = do not fire by default.")
305
- }).describe("Rule-based strategy. Evaluates rules top-to-bottom. First rule where ALL conditions match returns its value. If no rule matches, returns default. For triggerWhen: set value=true on matching rules, default=false.");
306
- var ScoreStrategyZ = z.object({
307
- type: z.literal("score"),
308
- field: z.string(),
309
- threshold: z.number(),
310
- above: z.unknown(),
311
- below: z.unknown()
312
- }).describe("Score-based strategy. Compares a field value against a threshold.");
313
- var ModelStrategyZ = z.object({
314
- type: z.literal("model"),
315
- modelId: z.string(),
316
- inputs: z.array(z.string()),
317
- outputMapping: z.record(z.string(), z.unknown()),
318
- default: z.unknown()
319
- }).describe("ML model strategy. Sends inputs to a model and maps outputs.");
320
- var ExternalStrategyZ = z.object({
321
- type: z.literal("external"),
322
- endpoint: z.string(),
323
- method: z.enum(["GET", "POST"]).optional(),
324
- default: z.unknown(),
325
- timeoutMs: z.number().optional()
326
- }).describe("External API strategy. Calls an endpoint to determine the value.");
327
- var DecisionStrategyZ = z.discriminatedUnion("type", [
328
- RuleStrategyZ,
329
- ScoreStrategyZ,
330
- ModelStrategyZ,
331
- ExternalStrategyZ
332
- ]);
333
- var TriggerWhenZ = DecisionStrategyZ.nullable().optional();
334
- var EventScopeZ = z.object({
335
- events: z.array(z.string()),
336
- urlContains: z.string().optional(),
337
- props: z.record(z.union([z.string(), z.number(), z.boolean()])).optional()
338
- });
339
- var NotifyZ = z.object({
340
- title: z.string().optional().describe("Notification title"),
341
- body: z.string().optional().describe("Notification body text"),
342
- icon: z.string().optional().describe("Notification icon (emoji or URL)")
343
- }).describe("Optional toast notification shown when this action triggers.").nullable().optional();
344
-
345
- // ../../sdk-contracts/dist/telemetry-events.js
346
- var entry = (e2) => e2;
347
- var TELEMETRY_EVENTS = {
348
- // --- intervention metrics (April 2026) — wire names unchanged, all plan-scope
349
- syntro_config_served: entry({
350
- subject: "config",
351
- verb: "served",
352
- scope: "plan",
353
- props: ["tiles", "actions"]
354
- }),
355
- syntro_intervention_seen: entry({
356
- subject: "intervention",
357
- verb: "seen",
358
- scope: "plan",
359
- props: ["intervention_id", "intervention_kind"]
360
- }),
361
- syntro_intervention_triggered: entry({
362
- subject: "intervention",
363
- verb: "triggered",
364
- scope: "plan",
365
- props: ["intervention_id", "intervention_kind"]
366
- }),
367
- syntro_intervention_interacted: entry({
368
- subject: "intervention",
369
- verb: "interacted",
370
- scope: "plan",
371
- props: ["intervention_id", "intervention_kind", "interaction_type"]
372
- }),
373
- // --- concierge (FEAT-1789150184) ---------------------------------------
374
- syntro_concierge_opened: entry({
375
- subject: "concierge",
376
- verb: "opened",
377
- scope: "open",
378
- props: ["trigger", "page_path"]
379
- // trigger: ConciergeOpenTrigger
380
- }),
381
- syntro_concierge_closed: entry({
382
- subject: "concierge",
383
- verb: "closed",
384
- scope: "open",
385
- props: ["duration_ms", "reason", "messages_sent", "tiles_dismissed", "pages_viewed"]
386
- // reason: ConciergeCloseReason
387
- }),
388
- syntro_deck_paged: entry({
389
- subject: "deck",
390
- verb: "paged",
391
- scope: "open",
392
- props: ["from_index", "to_index", "method"]
393
- // method: DeckPageMethod
394
- }),
395
- syntro_tile_dismissed: entry({
396
- subject: "tile",
397
- verb: "dismissed",
398
- scope: "open",
399
- props: ["tile_id", "tile_kind", "visible_ms"]
400
- }),
401
- // The three chat atoms are OPEN-scope: they carry `surface` / `open_id` and
402
- // only make sense inside a concierge open. An INLINE chat bar (no concierge)
403
- // still dispatches them on every turn, and the core drops each one and
404
- // increments the `telemetry_dropped` health counter. That is EXPECTED on an
405
- // inline-chat host page, not a bug — `telemetry_dropped` there mixes "real
406
- // defect" with "inline chat has no open scope", so do not alert on it alone.
407
- syntro_chat_message_sent: entry({
408
- subject: "chat_message",
409
- verb: "sent",
410
- scope: "open",
411
- props: ["turn", "chars", "via"]
412
- // via: ChatSendVia
413
- }),
414
- syntro_chat_reply_received: entry({
415
- subject: "chat_reply",
416
- verb: "received",
417
- scope: "open",
418
- props: ["turn", "ttft_ms", "total_ms", "mounts", "cards"]
419
- }),
420
- syntro_chat_interrupted: entry({
421
- subject: "chat",
422
- verb: "interrupted",
423
- scope: "open",
424
- props: ["turn", "after_ms"]
425
- }),
426
- syntro_chat_maximize_toggled: entry({
427
- subject: "chat_maximize",
428
- verb: "toggled",
429
- scope: "open",
430
- props: ["maximized"]
431
- }),
432
- syntro_chat_takeover_triggered: entry({
433
- subject: "chat_takeover",
434
- verb: "triggered",
435
- scope: "open",
436
- props: ["turn"]
437
- })
438
- };
439
- var TELEMETRY_EVENT_NAMES = Object.keys(TELEMETRY_EVENTS);
440
-
441
- // ../../sdk-contracts/dist/viewport.js
442
- var VIEWPORT_CLASS_ATTR = "data-viewport";
443
- var DESKTOP_MIN_WIDTH_PX = 720;
444
- function isCoarsePointer() {
445
- if (typeof window === "undefined" || typeof window.matchMedia !== "function")
446
- return true;
447
- return window.matchMedia("(pointer: coarse)").matches;
448
- }
449
- function classifyPointer() {
450
- return isCoarsePointer() ? "coarse" : "fine";
451
- }
452
- function hasAnyFinePointer() {
453
- if (typeof window === "undefined" || typeof window.matchMedia !== "function")
454
- return true;
455
- return window.matchMedia("(any-pointer: fine)").matches;
456
- }
457
- function observePointer(cb) {
458
- const read = () => ({ primary: classifyPointer(), anyFine: hasAnyFinePointer() });
459
- let current = read();
460
- cb(current);
461
- if (typeof window === "undefined" || typeof window.matchMedia !== "function")
462
- return () => {
463
- };
464
- const coarse = window.matchMedia("(pointer: coarse)");
465
- const anyFine = window.matchMedia("(any-pointer: fine)");
466
- const onChange = () => {
467
- const next = read();
468
- if (next.primary !== current.primary || next.anyFine !== current.anyFine) {
469
- current = next;
470
- cb(next);
471
- }
472
- };
473
- const sub = (mq) => {
474
- if (mq.addEventListener)
475
- mq.addEventListener("change", onChange);
476
- else
477
- mq.addListener?.(onChange);
478
- };
479
- const unsub = (mq) => {
480
- if (mq.removeEventListener)
481
- mq.removeEventListener("change", onChange);
482
- else
483
- mq.removeListener?.(onChange);
484
- };
485
- sub(coarse);
486
- sub(anyFine);
487
- return () => {
488
- unsub(coarse);
489
- unsub(anyFine);
490
- };
491
- }
492
- var POINTER_CLASS_ATTR = "data-pointer";
493
- function classifyViewport() {
494
- if (typeof window === "undefined")
495
- return "mobile";
496
- return window.innerWidth >= DESKTOP_MIN_WIDTH_PX ? "desktop" : "mobile";
497
- }
498
- function observeViewportClass(cb) {
499
- let current = classifyViewport();
500
- cb(current);
501
- if (typeof window === "undefined")
502
- return () => {
503
- };
504
- const onResize = () => {
505
- const next = classifyViewport();
506
- if (next !== current) {
507
- current = next;
508
- cb(next);
509
- }
510
- };
511
- window.addEventListener("resize", onResize, { passive: true });
512
- return () => window.removeEventListener("resize", onResize);
513
- }
514
- var ViewportClassController = class {
515
- constructor(host) {
516
- this.value = classifyViewport();
517
- this.pointer = classifyPointer();
518
- this.anyFinePointer = hasAnyFinePointer();
519
- this._unsubscribes = [];
520
- this._host = host;
521
- host.addController(this);
522
- }
523
- hostConnected() {
524
- this._unsubscribes = [
525
- observeViewportClass((c2) => {
526
- this.value = c2;
527
- this._host.setAttribute(VIEWPORT_CLASS_ATTR, c2);
528
- this._host.requestUpdate();
529
- }),
530
- observePointer(({ primary, anyFine }) => {
531
- this.pointer = primary;
532
- this.anyFinePointer = anyFine;
533
- this._host.setAttribute(POINTER_CLASS_ATTR, primary);
534
- this._host.requestUpdate();
535
- })
536
- ];
537
- }
538
- hostDisconnected() {
539
- for (const off of this._unsubscribes)
540
- off();
541
- this._unsubscribes = [];
542
- }
543
- };
17
+ stripMountPlumbing,
18
+ takeoverPlanSchema,
19
+ watchLivePrice
20
+ } from "./chunk-RUE5UUEN.js";
544
21
 
545
22
  // src/presets-store.ts
546
23
  function presetsWindow() {
@@ -577,9 +54,9 @@ function selectPoolItems(items, decision, max) {
577
54
  if (items.length <= max) return [...items];
578
55
  const sharp = decision && decision !== UNKNOWN_DECISION;
579
56
  if (sharp) {
580
- const matched = items.filter((i2) => tagsOf(i2).includes(decision));
581
- const general = items.filter((i2) => tagsOf(i2).length === 0);
582
- const rest = items.filter((i2) => !matched.includes(i2) && !general.includes(i2));
57
+ const matched = items.filter((i) => tagsOf(i).includes(decision));
58
+ const general = items.filter((i) => tagsOf(i).length === 0);
59
+ const rest = items.filter((i) => !matched.includes(i) && !general.includes(i));
583
60
  return [...matched, ...general, ...rest].slice(0, max);
584
61
  }
585
62
  const picked = [];
@@ -587,9 +64,9 @@ function selectPoolItems(items, decision, max) {
587
64
  for (const item of items) {
588
65
  const tags = tagsOf(item);
589
66
  if (tags.length === 0) continue;
590
- if (tags.some((t2) => covered.has(t2))) continue;
67
+ if (tags.some((t) => covered.has(t))) continue;
591
68
  picked.push(item);
592
- for (const t2 of tags) covered.add(t2);
69
+ for (const t of tags) covered.add(t);
593
70
  if (picked.length >= max) return picked.slice(0, max);
594
71
  }
595
72
  for (const item of items) {
@@ -1052,8 +529,8 @@ async function resolveScoredTemplate(opts) {
1052
529
  value = await opts.score(opts.scoreSpec);
1053
530
  }
1054
531
  const variant = opts.presets[value] ?? opts.presets[opts.fallbackValue ?? "unknown"] ?? {};
1055
- const setSlots = opts.slotIds.filter((s4) => s4 in variant);
1056
- const unsetSlots = opts.slotIds.filter((s4) => !(s4 in variant));
532
+ const setSlots = opts.slotIds.filter((s) => s in variant);
533
+ const unsetSlots = opts.slotIds.filter((s) => !(s in variant));
1057
534
  const pageOrder = opts.orderSlots ? opts.orderSlots([...opts.slotIds], (slot) => slot in variant) : opts.slotIds;
1058
535
  if (pageOrder.length > 0) {
1059
536
  opts.emitPlan(opts.buildPlan(pageOrder.map((slot) => ({ slot, preset: variant[slot] }))));
@@ -1068,6 +545,62 @@ async function resolveScoredTemplate(opts) {
1068
545
  return { value, decisionValue, setSlots, unsetSlots };
1069
546
  }
1070
547
 
548
+ // src/storefront-urls.ts
549
+ function storefrontBase(runtime2) {
550
+ if (!runtime2 || typeof runtime2 !== "object" || !("storefrontUrl" in runtime2)) return void 0;
551
+ if (typeof runtime2.storefrontUrl !== "string") return void 0;
552
+ try {
553
+ const base = new URL(runtime2.storefrontUrl);
554
+ return base.protocol === "https:" || base.protocol === "http:" ? base : void 0;
555
+ } catch {
556
+ return void 0;
557
+ }
558
+ }
559
+ function resolveAgainst(value, base) {
560
+ if (typeof value !== "string" || !value || /^(?:[a-z][a-z\d+.-]*:|\/\/|[?#])/i.test(value))
561
+ return value;
562
+ try {
563
+ const resolved = new URL(value, base);
564
+ return resolved.protocol === "https:" || resolved.protocol === "http:" ? resolved.href : value;
565
+ } catch {
566
+ return value;
567
+ }
568
+ }
569
+ function resolveStorePath(value, runtime2) {
570
+ const base = storefrontBase(runtime2);
571
+ return base ? resolveAgainst(value, base) : value;
572
+ }
573
+ function resolveStorefrontProps(props, runtime2) {
574
+ const base = storefrontBase(runtime2);
575
+ if (!base) return props;
576
+ const resolve = (value) => resolveAgainst(value, base);
577
+ const product = (value) => ({
578
+ ...value,
579
+ ...value.image ? { image: { ...value.image, src: resolve(value.image.src) } } : {},
580
+ ...Array.isArray(value.ctas) ? {
581
+ ctas: value.ctas.map((cta) => ({
582
+ ...cta,
583
+ ...cta.href ? { href: resolve(cta.href) } : {}
584
+ }))
585
+ } : {}
586
+ });
587
+ const content = props;
588
+ return {
589
+ ...props,
590
+ ...content.product ? { product: product(content.product) } : {},
591
+ ...Array.isArray(content.products) ? { products: content.products.map((value) => value ? product(value) : value) } : {},
592
+ ...Array.isArray(content.items) ? {
593
+ items: content.items.map(
594
+ (item) => item ? {
595
+ ...item,
596
+ ...item.image_url ? { image_url: resolve(item.image_url) } : {},
597
+ pdp_href: resolve(item.pdp_href)
598
+ } : item
599
+ )
600
+ } : {}
601
+ };
602
+ }
603
+
1071
604
  // src/takeover/resurrectionGuard.ts
1072
605
  function startResurrectionGuard(opts) {
1073
606
  const maxRetries = opts.maxRetries ?? 3;
@@ -1096,8 +629,8 @@ function startResurrectionGuard(opts) {
1096
629
  opts.onBudgetExhausted(exhausted.sectionId, exhausted.el);
1097
630
  return;
1098
631
  }
1099
- for (const { sectionId, el, n: n2 } of present) {
1100
- counts.set(sectionId, n2);
632
+ for (const { sectionId, el, n } of present) {
633
+ counts.set(sectionId, n);
1101
634
  opts.onResurrected(sectionId, el);
1102
635
  }
1103
636
  };
@@ -1154,7 +687,7 @@ var NOOP_DISPOSE = () => {
1154
687
  };
1155
688
  async function runTakeover(config, ports, opts = {}) {
1156
689
  if (!ports.hasSignals()) {
1157
- const declaredRoles = new Set(config.hostSections.map((s4) => s4.role));
690
+ const declaredRoles = new Set(config.hostSections.map((s) => s.role));
1158
691
  void Promise.resolve().then(() => ports.fetchPlan()).then(
1159
692
  (response2) => ports.renderModules(
1160
693
  {
@@ -1164,9 +697,9 @@ async function runTakeover(config, ports, opts = {}) {
1164
697
  // 1. host-derived entries (kind:sectionId) — review F2;
1165
698
  // 2. native kinds superseding a DECLARED host role (two-Q&As
1166
699
  // bug: our qa next to their attached FAQ).
1167
- order: response2.plan.order.filter((entry2) => {
1168
- if (entry2.includes(":")) return false;
1169
- const superseded = KIND_SUPERSEDES_ROLE[entry2];
700
+ order: response2.plan.order.filter((entry) => {
701
+ if (entry.includes(":")) return false;
702
+ const superseded = KIND_SUPERSEDES_ROLE[entry];
1170
703
  return !(superseded && declaredRoles.has(superseded));
1171
704
  }),
1172
705
  suppress: []
@@ -1200,7 +733,7 @@ async function runTakeover(config, ports, opts = {}) {
1200
733
  return { outcome: "aborted-precommit", dispose: NOOP_DISPOSE };
1201
734
  }
1202
735
  const plan = response.plan;
1203
- const sectionsById = new Map(config.hostSections.map((s4) => [s4.id, s4]));
736
+ const sectionsById = new Map(config.hostSections.map((s) => [s.id, s]));
1204
737
  const toSuppress = [];
1205
738
  let reservedPx = 0;
1206
739
  for (const id of plan.suppress) {
@@ -1272,9 +805,8 @@ async function runTakeover(config, ports, opts = {}) {
1272
805
  }
1273
806
 
1274
807
  // src/takeover/fetchPlan.ts
1275
- import { z as z2 } from "zod";
1276
- var takeoverPlanResponseSchema = z2.object({
1277
- decisionArchetype: z2.string().min(1),
808
+ var takeoverPlanResponseSchema = external_exports.object({
809
+ decisionArchetype: external_exports.string().min(1),
1278
810
  plan: takeoverPlanSchema
1279
811
  }).strict();
1280
812
  function createTakeoverPlanFetcher(deps) {
@@ -1343,7 +875,7 @@ function applyRegionCurtain(region, opts = {}) {
1343
875
  return lift;
1344
876
  }
1345
877
  function applyCurtainToTargets(targets, opts = {}) {
1346
- const lifts = targets.map((t2) => applyRegionCurtain(t2, opts));
878
+ const lifts = targets.map((t) => applyRegionCurtain(t, opts));
1347
879
  return () => {
1348
880
  for (const lift of lifts) lift();
1349
881
  };
@@ -1398,14 +930,20 @@ function attachLifecycleDispose(result) {
1398
930
 
1399
931
  // src/widgets/PdpSectionHeaderLit.ts
1400
932
  import { html, LitElement, nothing } from "lit";
1401
- function ensureSectionHeaderStyles() {
933
+
934
+ // src/widgets/ensureStylesInRoot.ts
935
+ function ensureStylesInRoot(el, id, css) {
1402
936
  if (typeof document === "undefined") return;
1403
- if (document.getElementById("syntro-pdp-section-header-style")) return;
1404
- const s4 = document.createElement("style");
1405
- s4.id = "syntro-pdp-section-header-style";
1406
- s4.textContent = SECTION_HEADER_CSS;
1407
- document.head.appendChild(s4);
937
+ const root = el.getRootNode();
938
+ const target = root instanceof ShadowRoot ? root : document.head;
939
+ if (target.querySelector(`#${id}`)) return;
940
+ const style = document.createElement("style");
941
+ style.id = id;
942
+ style.textContent = css;
943
+ target.appendChild(style);
1408
944
  }
945
+
946
+ // src/widgets/PdpSectionHeaderLit.ts
1409
947
  var SECTION_HEADER_CSS = `
1410
948
  /* The header renders at the FULL-WIDTH section (band) level so the eyebrow
1411
949
  can center across the whole horizontal slice, while the headline below
@@ -1530,7 +1068,7 @@ var PdpSectionHeaderLit = class extends LitElement {
1530
1068
  }
1531
1069
  connectedCallback() {
1532
1070
  super.connectedCallback();
1533
- ensureSectionHeaderStyles();
1071
+ ensureStylesInRoot(this, "syntro-pdp-section-header-style", SECTION_HEADER_CSS);
1534
1072
  }
1535
1073
  render() {
1536
1074
  const split = this.accent ? splitTitleAt(this.title, this.accent) : null;
@@ -1562,14 +1100,6 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-sec
1562
1100
 
1563
1101
  // src/widgets/PdpSlotLit.ts
1564
1102
  import { html as html2, LitElement as LitElement2, nothing as nothing2 } from "lit";
1565
- function ensureSlotStyles() {
1566
- if (typeof document === "undefined") return;
1567
- if (document.getElementById("syntro-pdp-slot-style")) return;
1568
- const s4 = document.createElement("style");
1569
- s4.id = "syntro-pdp-slot-style";
1570
- s4.textContent = SLOT_CSS;
1571
- document.head.appendChild(s4);
1572
- }
1573
1103
  var SLOT_CSS = `
1574
1104
  .syntro-pdp-slot {
1575
1105
  --sl-primary: var(--syntro-pdp-primary, hsl(150 32% 38%));
@@ -1731,7 +1261,7 @@ var PdpSlotLit = class extends LitElement2 {
1731
1261
  }
1732
1262
  connectedCallback() {
1733
1263
  super.connectedCallback();
1734
- ensureSlotStyles();
1264
+ ensureStylesInRoot(this, "syntro-pdp-slot-style", SLOT_CSS);
1735
1265
  }
1736
1266
  willUpdate() {
1737
1267
  this.dataset.state = this.state;
@@ -1774,23 +1304,23 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-slo
1774
1304
  }
1775
1305
 
1776
1306
  // src/takeover/renderModules.ts
1777
- function parsePlanEntry(entry2) {
1778
- const i2 = entry2.indexOf(":");
1779
- if (i2 === -1) return { kind: entry2, hostSectionId: null };
1780
- return { kind: entry2.slice(0, i2), hostSectionId: entry2.slice(i2 + 1) || null };
1307
+ function parsePlanEntry(entry) {
1308
+ const i = entry.indexOf(":");
1309
+ if (i === -1) return { kind: entry, hostSectionId: null };
1310
+ return { kind: entry.slice(0, i), hostSectionId: entry.slice(i + 1) || null };
1781
1311
  }
1782
1312
  function createTakeoverModuleRenderer(deps) {
1783
1313
  return async (plan, onModuleFailed) => {
1784
1314
  const decisionArchetype = deps.getDecisionArchetype();
1785
1315
  const row = deps.getPresetRow(decisionArchetype) ?? {};
1786
1316
  const bakedWork = [];
1787
- plan.order.forEach((entry2, index) => {
1788
- const { kind, hostSectionId } = parsePlanEntry(entry2);
1317
+ plan.order.forEach((entry, index) => {
1318
+ const { kind, hostSectionId } = parsePlanEntry(entry);
1789
1319
  const slotId = `pdp-slot-${kind}`;
1790
1320
  const preset = row[slotId];
1791
1321
  const tag = deps.resolveTag(kind, preset?.variant ?? null);
1792
1322
  if (!tag) {
1793
- deps.telemetry("takeover.module_kind_unknown", { entry: entry2, kind });
1323
+ deps.telemetry("takeover.module_kind_unknown", { entry, kind });
1794
1324
  onModuleFailed(hostSectionId);
1795
1325
  return;
1796
1326
  }
@@ -1799,7 +1329,7 @@ function createTakeoverModuleRenderer(deps) {
1799
1329
  const section = document.createElement("section");
1800
1330
  section.toggleAttribute("data-syntro-pdp-section", true);
1801
1331
  section.setAttribute("data-slot", slotId);
1802
- section.setAttribute("data-syntro-takeover-entry", entry2);
1332
+ section.setAttribute("data-syntro-takeover-entry", entry);
1803
1333
  const header = document.createElement("syntro-pdp-section-header");
1804
1334
  header.eyebrow = task.header.eyebrow ?? "";
1805
1335
  header.title = task.header.title ?? "";
@@ -1820,7 +1350,7 @@ function createTakeoverModuleRenderer(deps) {
1820
1350
  } else {
1821
1351
  slotEl.state = "failed";
1822
1352
  }
1823
- deps.telemetry("takeover.module_failed", { entry: entry2, kind });
1353
+ deps.telemetry("takeover.module_failed", { entry, kind });
1824
1354
  onModuleFailed(hostSectionId);
1825
1355
  };
1826
1356
  const mountPayload = async (payload) => {
@@ -1857,14 +1387,6 @@ var RELATIONSHIP_LABELS = {
1857
1387
  complement: "Pairs with this",
1858
1388
  alternative: "Alternative"
1859
1389
  };
1860
- function ensureOtherProductTileStyles() {
1861
- if (typeof document === "undefined") return;
1862
- if (document.getElementById("syntro-other-product-tile-style")) return;
1863
- const s4 = document.createElement("style");
1864
- s4.id = "syntro-other-product-tile-style";
1865
- s4.textContent = OTHER_PRODUCT_TILE_CSS;
1866
- document.head.appendChild(s4);
1867
- }
1868
1390
  var OTHER_PRODUCT_TILE_CSS = `
1869
1391
  .opt-card {
1870
1392
  --opt-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -1909,7 +1431,7 @@ var OTHER_PRODUCT_TILE_CSS = `
1909
1431
  font-size: 10px;
1910
1432
  text-transform: uppercase;
1911
1433
  letter-spacing: 0.14em;
1912
- color: var(--opt-primary);
1434
+ color: var(--syntro-pdp-primary-ink, var(--opt-primary));
1913
1435
  font-weight: 600;
1914
1436
  display: flex;
1915
1437
  align-items: center;
@@ -1994,64 +1516,107 @@ var OTHER_PRODUCT_TILE_CSS = `
1994
1516
  background: color-mix(in srgb, var(--opt-primary) 10%, transparent);
1995
1517
  }
1996
1518
  `;
1519
+ var _unwatch, _OtherProductTileLit_instances, watchPrice_fn;
1997
1520
  var OtherProductTileLit = class extends LitElement3 {
1998
1521
  constructor() {
1999
1522
  super(...arguments);
1523
+ __privateAdd(this, _OtherProductTileLit_instances);
2000
1524
  this.tile = null;
1525
+ /** The store's live display price, painted over the catalog price. */
1526
+ this._livePrice = null;
1527
+ __privateAdd(this, _unwatch, null);
1528
+ /**
1529
+ * View asks whoever holds the page to open this product. The event crosses
1530
+ * shadow roots, so a canvas that mounts the page can answer it; on a page
1531
+ * where nothing listens, the button does nothing.
1532
+ */
1533
+ this._onView = () => {
1534
+ if (!this.tile) return;
1535
+ const { recommended_product_id: productId, name, image_url, price_cents } = this.tile;
1536
+ const price = this._livePrice ?? this.tile.price;
1537
+ this.dispatchEvent(
1538
+ new CustomEvent("syntro:view-product", {
1539
+ bubbles: true,
1540
+ composed: true,
1541
+ detail: { productId, name, image_url, price, price_cents }
1542
+ })
1543
+ );
1544
+ };
1545
+ }
1546
+ willUpdate(changed) {
1547
+ if (!changed.has("tile")) return;
1548
+ this._livePrice = null;
1549
+ if (this.isConnected) __privateMethod(this, _OtherProductTileLit_instances, watchPrice_fn).call(this);
1550
+ }
1551
+ disconnectedCallback() {
1552
+ var _a;
1553
+ super.disconnectedCallback();
1554
+ (_a = __privateGet(this, _unwatch)) == null ? void 0 : _a.call(this);
1555
+ __privateSet(this, _unwatch, null);
2001
1556
  }
2002
1557
  createRenderRoot() {
2003
1558
  return this;
2004
1559
  }
2005
1560
  connectedCallback() {
2006
1561
  super.connectedCallback();
2007
- ensureOtherProductTileStyles();
1562
+ ensureStylesInRoot(this, "syntro-other-product-tile-style", OTHER_PRODUCT_TILE_CSS);
1563
+ __privateMethod(this, _OtherProductTileLit_instances, watchPrice_fn).call(this);
2008
1564
  }
2009
1565
  render() {
2010
- const t2 = this.tile;
2011
- if (!t2) {
1566
+ const t = this.tile;
1567
+ if (!t) {
2012
1568
  return html3``;
2013
1569
  }
2014
- const relationshipLabel = RELATIONSHIP_LABELS[t2.relationship];
2015
- const price = t2.price_cents == null ? "" : `$${(t2.price_cents / 100).toFixed(0)}`;
1570
+ const relationshipLabel = RELATIONSHIP_LABELS[t.relationship];
1571
+ const price = this._livePrice ?? t.price ?? "";
2016
1572
  return html3`
2017
- <article class="opt-card" data-relationship="${t2.relationship}">
2018
- ${t2.image_url ? html3`<div class="opt-image">
2019
- <img src="${t2.image_url}" alt="${t2.name}" loading="lazy" />
1573
+ <article class="opt-card" data-relationship="${t.relationship}">
1574
+ ${t.image_url ? html3`<div class="opt-image">
1575
+ <img src="${t.image_url}" alt="${t.name}" loading="lazy" />
2020
1576
  </div>` : null}
2021
- <div class="opt-relationship" data-relationship="${t2.relationship}">
1577
+ <div class="opt-relationship" data-relationship="${t.relationship}">
2022
1578
  ${relationshipLabel ? html3`${relationshipLabel}` : null}
2023
- <span class="opt-relationship-type">${t2.relationship}</span>
1579
+ <span class="opt-relationship-type">${t.relationship}</span>
2024
1580
  </div>
2025
- <h3 class="opt-name">${t2.name}</h3>
2026
- ${t2.tagline ? html3`<p class="opt-tagline">${t2.tagline}</p>` : null}
2027
- ${t2.why_blurb ? html3`<p class="opt-why">${t2.why_blurb}</p>` : null}
2028
- ${t2.evidence_chips.length ? html3`<div class="opt-chips">
2029
- ${t2.evidence_chips.map((c2) => html3`<span class="opt-chip">${c2.display}</span>`)}
1581
+ <h3 class="opt-name">${t.name}</h3>
1582
+ ${t.tagline ? html3`<p class="opt-tagline">${t.tagline}</p>` : null}
1583
+ ${t.why_blurb ? html3`<p class="opt-why">${t.why_blurb}</p>` : null}
1584
+ ${t.evidence_chips.length ? html3`<div class="opt-chips">
1585
+ ${t.evidence_chips.map((c) => html3`<span class="opt-chip">${c.display}</span>`)}
2030
1586
  </div>` : null}
2031
1587
  <div class="opt-footer">
2032
1588
  <span class="opt-price">${price}</span>
2033
- <button type="button" class="opt-cta" data-action="view-product">View</button>
1589
+ <button type="button" class="opt-cta" data-action="view-product" @click=${this._onView}>
1590
+ View
1591
+ </button>
2034
1592
  </div>
2035
1593
  </article>
2036
1594
  `;
2037
1595
  }
2038
1596
  };
1597
+ _unwatch = new WeakMap();
1598
+ _OtherProductTileLit_instances = new WeakSet();
1599
+ /** The store's live price, now and whenever the runtime publishes a
1600
+ * commerce adapter; no answer keeps the catalog price the server sent. */
1601
+ watchPrice_fn = function() {
1602
+ var _a;
1603
+ (_a = __privateGet(this, _unwatch)) == null ? void 0 : _a.call(this);
1604
+ __privateSet(this, _unwatch, null);
1605
+ const id = this.tile?.recommended_product_id;
1606
+ if (!id || !this.tile?.price) return;
1607
+ __privateSet(this, _unwatch, watchLivePrice(id, (live) => {
1608
+ if (this.tile?.recommended_product_id === id) this._livePrice = live.price;
1609
+ }));
1610
+ };
2039
1611
  OtherProductTileLit.properties = {
2040
- tile: { attribute: false }
1612
+ tile: { attribute: false },
1613
+ _livePrice: { state: true }
2041
1614
  };
2042
1615
  if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-other-product-tile")) {
2043
1616
  customElements.define("syntro-pdp-other-product-tile", OtherProductTileLit);
2044
1617
  }
2045
1618
 
2046
1619
  // src/widgets/PdpOtherProductsCarouselLit.ts
2047
- function ensureCarouselStyles() {
2048
- if (typeof document === "undefined") return;
2049
- if (document.getElementById("syntro-pdp-other-products-carousel-style")) return;
2050
- const s4 = document.createElement("style");
2051
- s4.id = "syntro-pdp-other-products-carousel-style";
2052
- s4.textContent = CAROUSEL_CSS;
2053
- document.head.appendChild(s4);
2054
- }
2055
1620
  var CAROUSEL_CSS = `
2056
1621
  /* Host containment \u2014 must not bust the PDP container width. */
2057
1622
  syntro-pdp-other-products-carousel {
@@ -2125,7 +1690,7 @@ var PdpOtherProductsCarouselLit = class extends LitElement4 {
2125
1690
  }
2126
1691
  connectedCallback() {
2127
1692
  super.connectedCallback();
2128
- ensureCarouselStyles();
1693
+ ensureStylesInRoot(this, "syntro-pdp-other-products-carousel-style", CAROUSEL_CSS);
2129
1694
  }
2130
1695
  render() {
2131
1696
  if (!this.data) return html4``;
@@ -2153,14 +1718,6 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-oth
2153
1718
 
2154
1719
  // src/widgets/PdpOtherProductsGridLit.ts
2155
1720
  import { html as html5, LitElement as LitElement5 } from "lit";
2156
- function ensureGridStyles() {
2157
- if (typeof document === "undefined") return;
2158
- if (document.getElementById("syntro-pdp-other-products-grid-style")) return;
2159
- const s4 = document.createElement("style");
2160
- s4.id = "syntro-pdp-other-products-grid-style";
2161
- s4.textContent = GRID_CSS;
2162
- document.head.appendChild(s4);
2163
- }
2164
1721
  var GRID_CSS = `
2165
1722
  syntro-pdp-other-products-grid {
2166
1723
  display: block;
@@ -2197,7 +1754,7 @@ var PdpOtherProductsGridLit = class extends LitElement5 {
2197
1754
  }
2198
1755
  connectedCallback() {
2199
1756
  super.connectedCallback();
2200
- ensureGridStyles();
1757
+ ensureStylesInRoot(this, "syntro-pdp-other-products-grid-style", GRID_CSS);
2201
1758
  }
2202
1759
  render() {
2203
1760
  if (!this.data) return html5``;
@@ -2235,7 +1792,7 @@ function _readPdpDecisionArchetypes() {
2235
1792
  if (typeof window === "undefined") return void 0;
2236
1793
  return window.__SYNTRO_PDP_DECISION_ARCHETYPES__;
2237
1794
  }
2238
- async function composeModule(authedFetch, args) {
1795
+ async function composeModule(authedFetch, args, signal) {
2239
1796
  try {
2240
1797
  const requestBody = {
2241
1798
  product_name: args.productName,
@@ -2254,7 +1811,8 @@ async function composeModule(authedFetch, args) {
2254
1811
  };
2255
1812
  const resp = await authedFetch("/api/pdp/module", {
2256
1813
  method: "POST",
2257
- body: JSON.stringify(requestBody)
1814
+ body: JSON.stringify(requestBody),
1815
+ signal
2258
1816
  });
2259
1817
  if (!resp.ok) return null;
2260
1818
  const data = await resp.json();
@@ -2292,33 +1850,33 @@ function _titleCase(slug) {
2292
1850
  return slug.split(/[-_\s]+/).map((w) => w ? w[0].toUpperCase() + w.slice(1) : "").join(" ");
2293
1851
  }
2294
1852
  function _countSignals(signals) {
2295
- let n2 = 0;
1853
+ let n = 0;
2296
1854
  const walk = (v) => {
2297
1855
  if (v && typeof v === "object") {
2298
1856
  for (const x of Object.values(v)) walk(x);
2299
1857
  } else if (v !== "" && v != null) {
2300
- n2++;
1858
+ n++;
2301
1859
  }
2302
1860
  };
2303
1861
  walk(signals);
2304
- return n2;
1862
+ return n;
2305
1863
  }
2306
1864
  function _signalDimensions(signals) {
2307
- let n2 = 0;
1865
+ let n = 0;
2308
1866
  for (const v of Object.values(signals)) {
2309
1867
  if (v && typeof v === "object") {
2310
- if (Object.keys(v).length) n2++;
1868
+ if (Object.keys(v).length) n++;
2311
1869
  } else if (v !== "" && v != null && v !== false) {
2312
- n2++;
1870
+ n++;
2313
1871
  }
2314
1872
  }
2315
- return n2;
1873
+ return n;
2316
1874
  }
2317
1875
  function _fingerprint(signals) {
2318
- const s4 = JSON.stringify(signals);
1876
+ const s = JSON.stringify(signals);
2319
1877
  let h = 2166136261;
2320
- for (let i2 = 0; i2 < s4.length; i2++) {
2321
- h ^= s4.charCodeAt(i2);
1878
+ for (let i = 0; i < s.length; i++) {
1879
+ h ^= s.charCodeAt(i);
2322
1880
  h = Math.imul(h, 16777619);
2323
1881
  }
2324
1882
  return (h >>> 0).toString(16).padStart(8, "0");
@@ -2416,9 +1974,9 @@ function buildExplain(opts) {
2416
1974
  dossier
2417
1975
  };
2418
1976
  }
2419
- async function fetchDossier(authedFetch) {
1977
+ async function fetchDossier(authedFetch, signal) {
2420
1978
  try {
2421
- const resp = await authedFetch("/api/pdp/dossier", { method: "POST" });
1979
+ const resp = await authedFetch("/api/pdp/dossier", { method: "POST", signal });
2422
1980
  if (!resp.ok) return [];
2423
1981
  const data = await resp.json();
2424
1982
  const rows = data.metrics;
@@ -2558,8 +2116,8 @@ var _PAGE_TRAIL_MAX_CHARS = 600;
2558
2116
  function _renderPageTrail(history) {
2559
2117
  const paths = [];
2560
2118
  let length = 0;
2561
- for (let i2 = history.length - 1; i2 >= 0; i2--) {
2562
- let path = history[i2].url;
2119
+ for (let i = history.length - 1; i >= 0; i--) {
2120
+ let path = history[i].url;
2563
2121
  try {
2564
2122
  const u = new URL(path);
2565
2123
  path = `${u.pathname}${u.search}`;
@@ -2572,14 +2130,6 @@ function _renderPageTrail(history) {
2572
2130
  }
2573
2131
  return paths.join(" \u2192 ");
2574
2132
  }
2575
- function ensureSectionLayoutStyles() {
2576
- if (typeof document === "undefined") return;
2577
- if (document.getElementById("syntro-pdp-layout-style")) return;
2578
- const s4 = document.createElement("style");
2579
- s4.id = "syntro-pdp-layout-style";
2580
- s4.textContent = SECTION_LAYOUT_CSS;
2581
- document.head.appendChild(s4);
2582
- }
2583
2133
  var SECTION_LAYOUT_CSS = `
2584
2134
  [data-syntro-pdp-root] {
2585
2135
  --sc-pdp-max-w: var(--syntro-pdp-max-width, 1080px);
@@ -2760,6 +2310,7 @@ function resolveTagForMount(inst) {
2760
2310
  }
2761
2311
  return null;
2762
2312
  }
2313
+ var _abort;
2763
2314
  var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2764
2315
  constructor() {
2765
2316
  super(...arguments);
@@ -2840,6 +2391,15 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2840
2391
  * after reconnect must undo that before the engine renders again. */
2841
2392
  this._takeoverRanBefore = false;
2842
2393
  this._takeoverLateWarned = false;
2394
+ /**
2395
+ * Cancels in-flight /api/pdp/module + /api/pdp/dossier calls when the
2396
+ * element is removed mid-request — a canvas card can be torn down while a
2397
+ * fetch is still pending, and an unaborted response landing after teardown
2398
+ * would apply a mutation/mount to a disconnected element. Reset to a fresh
2399
+ * controller on (re)connect so a re-added element gets a live signal
2400
+ * instead of one that's already aborted from the previous lifecycle.
2401
+ */
2402
+ __privateAdd(this, _abort, new AbortController());
2843
2403
  /**
2844
2404
  * bfcache re-entry (review F4). pagehide disposes the run
2845
2405
  * (attachLifecycleDispose) but a bfcache restore does NOT re-fire
@@ -2872,8 +2432,8 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2872
2432
  warn: (code, props) => this._warnDecision(code, props),
2873
2433
  cleanup: () => this._cleanupDecisionEffects()
2874
2434
  });
2875
- this._onDossierToggle = (e2) => {
2876
- const open = e2.target.open;
2435
+ this._onDossierToggle = (e) => {
2436
+ const open = e.target.open;
2877
2437
  this._dispatchDecision({ type: "dossier-toggled", open });
2878
2438
  };
2879
2439
  /** Stamps data-viewport on this tag (central classification,
@@ -2909,6 +2469,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2909
2469
  return this;
2910
2470
  }
2911
2471
  connectedCallback() {
2472
+ if (__privateGet(this, _abort).signal.aborted) __privateSet(this, _abort, new AbortController());
2912
2473
  if (!this._takeoverModeLatched) {
2913
2474
  this._takeoverModeLatched = true;
2914
2475
  this._takeoverMode = Boolean(this.takeover);
@@ -2920,7 +2481,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2920
2481
  }
2921
2482
  if (this._takeoverMode) {
2922
2483
  super.connectedCallback();
2923
- ensureSectionLayoutStyles();
2484
+ ensureStylesInRoot(this, "syntro-pdp-layout-style", SECTION_LAYOUT_CSS);
2924
2485
  if (typeof window !== "undefined") {
2925
2486
  window.removeEventListener("pageshow", this._onTakeoverPageShow);
2926
2487
  window.addEventListener("pageshow", this._onTakeoverPageShow);
@@ -2930,7 +2491,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2930
2491
  }
2931
2492
  const reconnecting = this._decisionState.connection === "disconnected";
2932
2493
  super.connectedCallback();
2933
- ensureSectionLayoutStyles();
2494
+ ensureStylesInRoot(this, "syntro-pdp-layout-style", SECTION_LAYOUT_CSS);
2934
2495
  if (!this.productId) this._selfIdentify = true;
2935
2496
  this._refreshNativeSlots();
2936
2497
  this._syncDecisionRuntime(reconnecting);
@@ -2944,6 +2505,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2944
2505
  }
2945
2506
  }
2946
2507
  disconnectedCallback() {
2508
+ __privateGet(this, _abort).abort();
2947
2509
  if (this._takeoverMode) {
2948
2510
  super.disconnectedCallback();
2949
2511
  if (typeof window !== "undefined") {
@@ -3145,24 +2707,28 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3145
2707
  }),
3146
2708
  requestLiveModule: async (kind, decisionArchetype) => {
3147
2709
  if (!authedFetch) return null;
3148
- return composeModule(authedFetch, {
3149
- productName: this.productName,
3150
- // Guaranteed non-empty: _startTakeover gates on a missing product id.
3151
- productId: this.productId,
3152
- kind,
3153
- variant: null,
3154
- // The takeover round trip scores only the decision axis (the server
3155
- // resolves the plan from it); the sport-archetype axis is unscored
3156
- // on this path, so it is sent as an explicit 'unknown' — never a
3157
- // silently-invented value.
3158
- archetype: { slug: "unknown", description: "" },
3159
- decisionArchetype: decisionArchetype === "unknown" ? null : {
3160
- slug: decisionArchetype,
3161
- description: decisionDescriptions[decisionArchetype] ?? ""
2710
+ return composeModule(
2711
+ authedFetch,
2712
+ {
2713
+ productName: this.productName,
2714
+ // Guaranteed non-empty: _startTakeover gates on a missing product id.
2715
+ productId: this.productId,
2716
+ kind,
2717
+ variant: null,
2718
+ // The takeover round trip scores only the decision axis (the server
2719
+ // resolves the plan from it); the sport-archetype axis is unscored
2720
+ // on this path, so it is sent as an explicit 'unknown' — never a
2721
+ // silently-invented value.
2722
+ archetype: { slug: "unknown", description: "" },
2723
+ decisionArchetype: decisionArchetype === "unknown" ? null : {
2724
+ slug: decisionArchetype,
2725
+ description: decisionDescriptions[decisionArchetype] ?? ""
2726
+ },
2727
+ behaviorSummary: "",
2728
+ signals
3162
2729
  },
3163
- behaviorSummary: "",
3164
- signals
3165
- });
2730
+ __privateGet(this, _abort).signal
2731
+ );
3166
2732
  },
3167
2733
  telemetry: telemetry2,
3168
2734
  getDecisionArchetype: () => scored.decisionArchetype
@@ -3329,13 +2895,35 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3329
2895
  }
3330
2896
  container.innerHTML = "";
3331
2897
  const el = document.createElement(tag);
3332
- el.data = inst.content;
2898
+ el.data = this._withStoreImages(tag, inst.content);
3333
2899
  container.appendChild(el);
3334
2900
  this._mountDurations.set(slotId, Math.round(performance.now() - slotStartedAt));
3335
2901
  outcomes.push({ status: "mounted", slot: slotId });
3336
2902
  }
3337
2903
  return outcomes;
3338
2904
  }
2905
+ /**
2906
+ * The cross-sell tiles' images are store-relative catalog paths. Inside an
2907
+ * app host's card the document is the host's sandbox and <base> is blocked,
2908
+ * so each is resolved against the runtime's storefront before the carousel
2909
+ * or grid gets it. On a storefront page there is no storefrontUrl and
2910
+ * nothing changes.
2911
+ */
2912
+ _withStoreImages(tag, content) {
2913
+ if (tag !== "syntro-pdp-other-products-carousel" && tag !== "syntro-pdp-other-products-grid")
2914
+ return content;
2915
+ const tiles = content?.tiles;
2916
+ if (!Array.isArray(tiles)) return content;
2917
+ return {
2918
+ ...content,
2919
+ tiles: tiles.map(
2920
+ (tile) => tile && typeof tile === "object" && "image_url" in tile ? {
2921
+ ...tile,
2922
+ image_url: resolveStorePath(tile.image_url, this.runtimeRef)
2923
+ } : tile
2924
+ )
2925
+ };
2926
+ }
3339
2927
  /** Render from config: score the archetype → mount the preset set-values. */
3340
2928
  async _fireFromConfig(productPresets, score, authedFetch, generation, run, scoreMulti) {
3341
2929
  const startedAt = performance.now();
@@ -3353,7 +2941,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3353
2941
  let scoredArgs = null;
3354
2942
  const composeAdapter = createPdpComposeAdapter({
3355
2943
  scoreSummary: () => authedFetch ? score({ model_id: "summary", name: "behavior_summary", type: "string", signals }) : Promise.resolve("unknown"),
3356
- requestDossier: () => authedFetch ? fetchDossier(authedFetch) : Promise.resolve([]),
2944
+ requestDossier: () => authedFetch ? fetchDossier(authedFetch, __privateGet(this, _abort).signal) : Promise.resolve([]),
3357
2945
  resolveTemplate: (_currentGeneration, composeUnset) => resolveScoredTemplate({
3358
2946
  // Wrap the scorer so the resolved SPORT archetype lands on the
3359
2947
  // element the instant it scores — the native-takeover check needs it
@@ -3441,22 +3029,26 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3441
3029
  requestModule: async ({ slot, archetype, decisionArchetype, behaviorSummary }) => {
3442
3030
  if (!authedFetch || !run.isCurrent()) return;
3443
3031
  const kind = slot.replace("pdp-slot-", "");
3444
- const payload = await composeModule(authedFetch, {
3445
- productName: this.productName,
3446
- productId: this.productId || null,
3447
- kind,
3448
- variant: null,
3449
- archetype: {
3450
- slug: archetype,
3451
- description: archetypeDescriptions[archetype] ?? ""
3032
+ const payload = await composeModule(
3033
+ authedFetch,
3034
+ {
3035
+ productName: this.productName,
3036
+ productId: this.productId || null,
3037
+ kind,
3038
+ variant: null,
3039
+ archetype: {
3040
+ slug: archetype,
3041
+ description: archetypeDescriptions[archetype] ?? ""
3042
+ },
3043
+ decisionArchetype: decisionArchetype === "unknown" ? null : {
3044
+ slug: decisionArchetype,
3045
+ description: decisionDescriptions[decisionArchetype] ?? ""
3046
+ },
3047
+ behaviorSummary,
3048
+ signals
3452
3049
  },
3453
- decisionArchetype: decisionArchetype === "unknown" ? null : {
3454
- slug: decisionArchetype,
3455
- description: decisionDescriptions[decisionArchetype] ?? ""
3456
- },
3457
- behaviorSummary,
3458
- signals
3459
- });
3050
+ __privateGet(this, _abort).signal
3051
+ );
3460
3052
  if (!run.isCurrent()) return;
3461
3053
  if (!payload) {
3462
3054
  run.dispatch({ type: "module-failed", generation, slot });
@@ -3582,7 +3174,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3582
3174
  _renderWhyBox(view) {
3583
3175
  if (view.status === "hidden") return "";
3584
3176
  if (view.status === "loading") return this._renderWhyBoxLoading();
3585
- const e2 = view.explanation;
3177
+ const e = view.explanation;
3586
3178
  return html6`
3587
3179
  <style>
3588
3180
  /* Token bridge: every color resolves from a --syntro-pdp-* var the
@@ -3624,7 +3216,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3624
3216
  [data-syntro-pdp-why][open] > summary { margin-bottom: 6px; }
3625
3217
  [data-syntro-pdp-why-note] { font-size: 11px; color: var(--syntro-pdp-muted, #666); margin: 0 0 4px; }
3626
3218
  [data-syntro-pdp-why-archetype] { font-size: 13px; margin: 3px 0; color: var(--syntro-pdp-fg, #1a1a1a); }
3627
- [data-syntro-pdp-why-archetype] strong { color: var(--syntro-pdp-primary, #1a1a1a); }
3219
+ [data-syntro-pdp-why-archetype] strong { color: var(--syntro-pdp-primary-ink, var(--syntro-pdp-primary, #1a1a1a)); }
3628
3220
  [data-syntro-pdp-why-summary] {
3629
3221
  font-size: 12.5px; font-style: italic; color: var(--syntro-pdp-muted, #666); margin: 6px 0 8px;
3630
3222
  border-left: 2px solid var(--syntro-pdp-accent, #555); padding-left: 10px;
@@ -3670,14 +3262,14 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3670
3262
  Shown in demo mode only — a peek under the hood at the live personalization.
3671
3263
  </div>
3672
3264
  <div data-syntro-pdp-why-archetype>
3673
- Shopping as: <strong>${e2.archetypeLabel}</strong>${e2.archetypeDesc ? html6` — ${e2.archetypeDesc}` : ""}
3265
+ Shopping as: <strong>${e.archetypeLabel}</strong>${e.archetypeDesc ? html6` — ${e.archetypeDesc}` : ""}
3674
3266
  </div>
3675
- ${e2.decisionLabel ? html6`<div data-syntro-pdp-why-archetype>
3676
- What they're weighing: <strong>${e2.decisionLabel}</strong>${e2.decisionDesc ? html6` — “${e2.decisionDesc}”` : ""}
3267
+ ${e.decisionLabel ? html6`<div data-syntro-pdp-why-archetype>
3268
+ What they're weighing: <strong>${e.decisionLabel}</strong>${e.decisionDesc ? html6` — “${e.decisionDesc}”` : ""}
3677
3269
  </div>` : ""}
3678
- ${e2.summary ? html6`<div data-syntro-pdp-why-summary>"${e2.summary}"</div>` : ""}
3270
+ ${e.summary ? html6`<div data-syntro-pdp-why-summary>"${e.summary}"</div>` : ""}
3679
3271
  <div data-syntro-pdp-why-metrics>
3680
- ${e2.metrics.map(
3272
+ ${e.metrics.map(
3681
3273
  (m) => html6`<div data-syntro-pdp-why-metric>
3682
3274
  <span data-syntro-pdp-why-metric-value>${m.value}</span>
3683
3275
  <span data-syntro-pdp-why-metric-label>${m.label}</span>
@@ -3760,10 +3352,10 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3760
3352
  ${skeletonStyles}
3761
3353
  <div data-syntro-pdp-root data-state="pending" data-syntro-pdp-skeleton>
3762
3354
  ${[0, 1, 2].map(
3763
- (i2) => html6`
3355
+ (i) => html6`
3764
3356
  <section
3765
3357
  data-syntro-pdp-skeleton-section
3766
- style="animation-delay: ${i2 * 0.15}s"
3358
+ style="animation-delay: ${i * 0.15}s"
3767
3359
  >
3768
3360
  <div data-syntro-pdp-skeleton-header>
3769
3361
  <div data-syntro-pdp-skeleton-eyebrow></div>
@@ -3820,6 +3412,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3820
3412
  `;
3821
3413
  }
3822
3414
  };
3415
+ _abort = new WeakMap();
3823
3416
  _SyntroPdpLit.properties = {
3824
3417
  productId: { type: String, attribute: "product-id" },
3825
3418
  productName: { type: String, attribute: "product-name" },
@@ -3833,9 +3426,9 @@ _SyntroPdpLit.properties = {
3833
3426
  /**
3834
3427
  * Self-contained skeleton (shimmer) CSS. Rendered inside the component
3835
3428
  * during the pre-plan / streaming phase. The global section-layout +
3836
- * per-slot accent rules live in the head-injected `SECTION_LAYOUT_CSS`
3837
- * (see `ensureSectionLayoutStyles`), not here, so they're emitted once
3838
- * per document rather than once per `<syntro-pdp>` instance.
3429
+ * per-slot accent rules live in `SECTION_LAYOUT_CSS`, injected once per
3430
+ * root via `ensureStylesInRoot`, not here, so they're emitted once per
3431
+ * root rather than once per `<syntro-pdp>` instance.
3839
3432
  */
3840
3433
  _SyntroPdpLit._skeletonStyles = html6`
3841
3434
  <style>
@@ -4258,14 +3851,14 @@ function collectFromNode(node) {
4258
3851
  const containers = [];
4259
3852
  if (node.matches(`[${NATIVE_SLOT_ATTR}]`)) containers.push(node);
4260
3853
  containers.push(...node.querySelectorAll(`[${NATIVE_SLOT_ATTR}]`));
4261
- for (const c2 of containers) {
4262
- if (c2.closest("syntro-pdp")) continue;
4263
- const pid = nativeProductId(c2);
3854
+ for (const c of containers) {
3855
+ if (c.closest("syntro-pdp")) continue;
3856
+ const pid = nativeProductId(c);
4264
3857
  if (pid) pending.add(pid);
4265
3858
  }
4266
3859
  }
4267
- var onDemoToggle = (e2) => {
4268
- const detail = e2.detail;
3860
+ var onDemoToggle = (e) => {
3861
+ const detail = e.detail;
4269
3862
  if (!detail || typeof detail.on !== "boolean") return;
4270
3863
  toggleOn = detail.on;
4271
3864
  renderSession();
@@ -4341,9 +3934,9 @@ async function resolveDomSelectorImage(input, _signal) {
4341
3934
  // src/bind/jsonld.ts
4342
3935
  function isProduct(node) {
4343
3936
  if (typeof node !== "object" || node === null) return false;
4344
- const t2 = node["@type"];
4345
- if (typeof t2 === "string") return t2 === "Product";
4346
- if (Array.isArray(t2)) return t2.includes("Product");
3937
+ const t = node["@type"];
3938
+ if (typeof t === "string") return t === "Product";
3939
+ if (Array.isArray(t)) return t.includes("Product");
4347
3940
  return false;
4348
3941
  }
4349
3942
  function parseScripts(scripts) {
@@ -4358,7 +3951,7 @@ function parseScripts(scripts) {
4358
3951
  while (stack.length) {
4359
3952
  const cur = stack.pop();
4360
3953
  if (Array.isArray(cur)) {
4361
- for (const c2 of cur) stack.push(c2);
3954
+ for (const c of cur) stack.push(c);
4362
3955
  continue;
4363
3956
  }
4364
3957
  if (isProduct(cur)) return cur;
@@ -4563,14 +4156,15 @@ async function resolveImageBind(input, signal) {
4563
4156
  }
4564
4157
 
4565
4158
  // src/bind/controller.ts
4566
- var _abort, _pending, _destroyed, _BindController_instances, resolveImage_fn, resolvePrice_fn;
4159
+ var _abort2, _pending, _unwatch2, _destroyed, _BindController_instances, resolveImage_fn, resolvePrice_fn;
4567
4160
  var BindController = class {
4568
4161
  constructor(products, callback) {
4569
4162
  this.products = products;
4570
4163
  this.callback = callback;
4571
4164
  __privateAdd(this, _BindController_instances);
4572
- __privateAdd(this, _abort, new AbortController());
4165
+ __privateAdd(this, _abort2, new AbortController());
4573
4166
  __privateAdd(this, _pending, []);
4167
+ __privateAdd(this, _unwatch2, []);
4574
4168
  __privateAdd(this, _destroyed, false);
4575
4169
  }
4576
4170
  start() {
@@ -4580,6 +4174,15 @@ var BindController = class {
4580
4174
  }
4581
4175
  if (product.price?.bind) {
4582
4176
  __privateGet(this, _pending).push(__privateMethod(this, _BindController_instances, resolvePrice_fn).call(this, product.id, product.price.bind));
4177
+ } else if (product.price || product.salePrice) {
4178
+ __privateGet(this, _unwatch2).push(
4179
+ watchLivePrice(product.id, (live) => {
4180
+ if (__privateGet(this, _destroyed)) return;
4181
+ this.callback(product.id, "price", live.price);
4182
+ this.callback(product.id, "compare_at", live.compareAt);
4183
+ })
4184
+ );
4185
+ __privateGet(this, _pending).push(fetchLivePrice(product.id).then(() => void 0));
4583
4186
  }
4584
4187
  }
4585
4188
  }
@@ -4589,23 +4192,42 @@ var BindController = class {
4589
4192
  destroy() {
4590
4193
  if (__privateGet(this, _destroyed)) return;
4591
4194
  __privateSet(this, _destroyed, true);
4592
- __privateGet(this, _abort).abort();
4195
+ __privateGet(this, _abort2).abort();
4196
+ for (const stop of __privateGet(this, _unwatch2)) stop();
4197
+ __privateSet(this, _unwatch2, []);
4593
4198
  }
4594
4199
  };
4595
- _abort = new WeakMap();
4200
+ _abort2 = new WeakMap();
4596
4201
  _pending = new WeakMap();
4202
+ _unwatch2 = new WeakMap();
4597
4203
  _destroyed = new WeakMap();
4598
4204
  _BindController_instances = new WeakSet();
4599
4205
  resolveImage_fn = async function(productId, bind) {
4600
- const value = await resolveImageBind(bind, __privateGet(this, _abort).signal);
4206
+ const value = await resolveImageBind(bind, __privateGet(this, _abort2).signal);
4601
4207
  if (__privateGet(this, _destroyed)) return;
4602
4208
  this.callback(productId, "image", value);
4603
4209
  };
4604
4210
  resolvePrice_fn = async function(productId, bind) {
4605
- const value = await resolvePriceBind(bind, __privateGet(this, _abort).signal);
4211
+ const value = await resolvePriceBind(bind, __privateGet(this, _abort2).signal);
4606
4212
  if (__privateGet(this, _destroyed)) return;
4607
4213
  this.callback(productId, "price", value);
4608
4214
  };
4215
+ function mergeResolved(resolved, productId, kind, value) {
4216
+ if (kind === "compare_at") {
4217
+ return { ...resolved, [productId]: { ...resolved[productId], compareAt: value } };
4218
+ }
4219
+ if (value == null) return resolved;
4220
+ return { ...resolved, [productId]: { ...resolved[productId], [kind]: value } };
4221
+ }
4222
+ function keepResolved(resolved, products) {
4223
+ const kept = {};
4224
+ for (const product of products) {
4225
+ const entry = resolved[product.id];
4226
+ if (!entry || product.price?.bind || !("compareAt" in entry)) continue;
4227
+ kept[product.id] = { price: entry.price, compareAt: entry.compareAt };
4228
+ }
4229
+ return kept;
4230
+ }
4609
4231
 
4610
4232
  // src/decision/card.ts
4611
4233
  var VARIANT_ERROR_COPY = {
@@ -4627,9 +4249,11 @@ function createInitialCardState(generation = 0) {
4627
4249
  variants: { status: "idle" },
4628
4250
  cart: { status: "idle" },
4629
4251
  selection: {},
4630
- activation: { status: "idle" }
4252
+ activation: { status: "idle" },
4253
+ optionsPinned: false
4631
4254
  };
4632
4255
  }
4256
+ var optionsOpen = (s) => s.face === "back" || s.optionsPinned;
4633
4257
  function selectCardView(state) {
4634
4258
  const primaryCta = state.cart.status === "added" ? {
4635
4259
  mode: "view-cart",
@@ -4639,7 +4263,7 @@ function selectCardView(state) {
4639
4263
  disabled: state.connection !== "connected" || state.commerce !== "available" || state.product.status !== "valid"
4640
4264
  };
4641
4265
  let panel = { status: "hidden", payload: null, error: null, retryable: false };
4642
- if (state.face === "back") {
4266
+ if (optionsOpen(state)) {
4643
4267
  switch (state.variants.status) {
4644
4268
  case "ready":
4645
4269
  panel = { status: "ready", payload: state.variants.payload, error: null, retryable: false };
@@ -4684,7 +4308,11 @@ function replaceProps(state, event) {
4684
4308
  return { state, effects: effects2 };
4685
4309
  }
4686
4310
  const generation = state.generation + 1;
4687
- const next = { ...createInitialCardState(generation), commerce: state.commerce };
4311
+ const next = {
4312
+ ...createInitialCardState(generation),
4313
+ commerce: state.commerce,
4314
+ optionsPinned: state.optionsPinned
4315
+ };
4688
4316
  const effects = [{ type: "cancel-flip-settle" }];
4689
4317
  if (!event.valid || event.productId === null) {
4690
4318
  return {
@@ -4717,7 +4345,13 @@ function activatePrimary(state, event) {
4717
4345
  // CTA is a plain navigating <a>, never dispatched here) — but the state
4718
4346
  // machine itself must also refuse to start the Woo/Shopify variant-
4719
4347
  // lookup flow for a product that has none.
4720
- !state.product.commerceCapable || state.face !== "front") {
4348
+ !state.product.commerceCapable || state.face !== "front" || // A pinned (double-wide) card has no primary CTA to activate — Add to
4349
+ // cart lives in the options panel beside the front face. Without this
4350
+ // guard a stray primary-activated dispatch after an add (cart status
4351
+ // 'added', face still 'front') would fall into the view-cart branch below
4352
+ // and emit close-sdk/open-cart/view_cart_clicked, which a pinned card must
4353
+ // never do (Task 1 keeps the card pinned open across the add).
4354
+ state.optionsPinned) {
4721
4355
  return unchanged2(state);
4722
4356
  }
4723
4357
  const productId = state.product.id;
@@ -4785,7 +4419,7 @@ function serializeSelection(selection) {
4785
4419
  };
4786
4420
  }
4787
4421
  function activateVariantAdd(state, event) {
4788
- if (state.connection !== "connected" || state.commerce !== "available" || state.product.status !== "valid" || state.face !== "back" || state.variants.status !== "ready" || state.cart.status === "pending" || state.cart.status === "added") {
4422
+ if (state.connection !== "connected" || state.commerce !== "available" || state.product.status !== "valid" || !optionsOpen(state) || state.variants.status !== "ready" || state.cart.status === "pending" || state.cart.status === "added") {
4789
4423
  return unchanged2(state);
4790
4424
  }
4791
4425
  const variant = state.variants.payload.variants.find(
@@ -4844,6 +4478,9 @@ function transitionCard(state, event) {
4844
4478
  const commerce = event.available ? "available" : "unavailable";
4845
4479
  return commerce === state.commerce ? unchanged2(state) : { state: { ...state, commerce }, effects: [] };
4846
4480
  }
4481
+ if (event.type === "options-pinned") {
4482
+ return event.pinned === state.optionsPinned ? unchanged2(state) : { state: { ...state, optionsPinned: event.pinned }, effects: [] };
4483
+ }
4847
4484
  if ((event.type === "variants-resolved" || event.type === "variants-failed" || event.type === "add-succeeded" || event.type === "add-failed") && (event.generation !== state.generation || state.connection !== "connected" || state.product.status !== "valid")) {
4848
4485
  return unchanged2(state);
4849
4486
  }
@@ -4884,7 +4521,7 @@ function transitionCard(state, event) {
4884
4521
  return { state: resumed.state, effects: [failureEffect, ...resumed.effects] };
4885
4522
  }
4886
4523
  case "selection-changed":
4887
- if (state.commerce !== "available" || state.face !== "back" || state.variants.status !== "ready") {
4524
+ if (state.commerce !== "available" || !optionsOpen(state) || state.variants.status !== "ready") {
4888
4525
  return unchanged2(state);
4889
4526
  }
4890
4527
  return {
@@ -5109,9 +4746,9 @@ function createCardEffectExecutor(ports) {
5109
4746
  var VIEW_DETAIL_ACTION_ID = "view_detail";
5110
4747
  function pickDetailCta(ctas) {
5111
4748
  if (!ctas?.length) return void 0;
5112
- const navigates = ctas.filter((c2) => !!c2.href);
4749
+ const navigates = ctas.filter((c) => !!c.href);
5113
4750
  if (!navigates.length) return void 0;
5114
- return navigates.find((c2) => c2.actionId === VIEW_DETAIL_ACTION_ID) ?? navigates.find((c2) => c2.variant && c2.variant !== "primary") ?? navigates[0];
4751
+ return navigates.find((c) => c.actionId === VIEW_DETAIL_ACTION_ID) ?? navigates.find((c) => c.variant && c.variant !== "primary") ?? navigates[0];
5115
4752
  }
5116
4753
 
5117
4754
  // src/sanitizer.ts
@@ -5157,12 +4794,12 @@ function isDangerousUrlAttr(name, value) {
5157
4794
  const normalized = normalizeUrlAttr(value);
5158
4795
  return normalized.startsWith("javascript:") || normalized.startsWith("vbscript:") || normalized.startsWith("data:");
5159
4796
  }
5160
- function sanitizeHtml(html31) {
4797
+ function sanitizeHtml(html32) {
5161
4798
  const hasNative = typeof window.Sanitizer === "function";
5162
4799
  if (hasNative) {
5163
4800
  try {
5164
- const s4 = new window.Sanitizer({});
5165
- const frag = s4.sanitizeToFragment(html31);
4801
+ const s = new window.Sanitizer({});
4802
+ const frag = s.sanitizeToFragment(html32);
5166
4803
  const div = document.createElement("div");
5167
4804
  div.append(frag);
5168
4805
  return div.innerHTML;
@@ -5170,7 +4807,7 @@ function sanitizeHtml(html31) {
5170
4807
  }
5171
4808
  }
5172
4809
  const tpl = document.createElement("template");
5173
- tpl.innerHTML = html31;
4810
+ tpl.innerHTML = html32;
5174
4811
  const root = tpl.content;
5175
4812
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
5176
4813
  const toRemove = [];
@@ -5197,17 +4834,65 @@ function sanitizeHtml(html31) {
5197
4834
  }
5198
4835
  return tpl.innerHTML;
5199
4836
  }
5200
-
5201
- // src/widgets/ensureStylesInRoot.ts
5202
- function ensureStylesInRoot(el, id, css) {
5203
- if (typeof document === "undefined") return;
5204
- const root = el.getRootNode();
5205
- const target = root instanceof ShadowRoot ? root : document.head;
5206
- if (target.querySelector(`#${id}`)) return;
5207
- const style = document.createElement("style");
5208
- style.id = id;
5209
- style.textContent = css;
5210
- target.appendChild(style);
4837
+ var DESCRIPTION_TAGS = /* @__PURE__ */ new Set([
4838
+ "h3",
4839
+ "h4",
4840
+ "h5",
4841
+ "h6",
4842
+ "p",
4843
+ "br",
4844
+ "ul",
4845
+ "ol",
4846
+ "li",
4847
+ "strong",
4848
+ "b",
4849
+ "em",
4850
+ "i",
4851
+ "u",
4852
+ "small",
4853
+ "sup",
4854
+ "sub",
4855
+ "table",
4856
+ "thead",
4857
+ "tbody",
4858
+ "tr",
4859
+ "th",
4860
+ "td",
4861
+ "blockquote"
4862
+ ]);
4863
+ var DEMOTED_HEADING = {
4864
+ h1: "h3",
4865
+ h2: "h3",
4866
+ h3: "h4",
4867
+ h4: "h5",
4868
+ h5: "h6",
4869
+ h6: "h6"
4870
+ };
4871
+ function sanitizeProductDescription(html32) {
4872
+ if (typeof html32 !== "string" || html32 === "") return "";
4873
+ const doc = new DOMParser().parseFromString(html32, "text/html");
4874
+ const out = document.createElement("div");
4875
+ const copy = (from, to) => {
4876
+ for (const child of Array.from(from.childNodes)) {
4877
+ if (child.nodeType === Node.TEXT_NODE) {
4878
+ to.appendChild(document.createTextNode(child.textContent ?? ""));
4879
+ continue;
4880
+ }
4881
+ if (!(child instanceof Element)) continue;
4882
+ const tag = child.tagName.toLowerCase();
4883
+ if (tag === "script" || tag === "style" || tag === "template" || tag === "iframe") continue;
4884
+ const kept = DEMOTED_HEADING[tag] ?? tag;
4885
+ if (DESCRIPTION_TAGS.has(kept)) {
4886
+ const el = document.createElement(kept);
4887
+ copy(child, el);
4888
+ to.appendChild(el);
4889
+ } else {
4890
+ copy(child, to);
4891
+ }
4892
+ }
4893
+ };
4894
+ copy(doc.body, out);
4895
+ return out.innerHTML;
5211
4896
  }
5212
4897
 
5213
4898
  // src/widgets/imageStandin.ts
@@ -5222,12 +4907,25 @@ function standinMonogram(name) {
5222
4907
  >`;
5223
4908
  }
5224
4909
 
4910
+ // src/widgets/presentationContext.ts
4911
+ function presentationFromContext(el) {
4912
+ let node = el;
4913
+ while (node) {
4914
+ if (node instanceof Element && node.getAttribute("data-product-presentation") === "double") {
4915
+ return "double";
4916
+ }
4917
+ const parent = node.parentNode;
4918
+ node = parent instanceof ShadowRoot ? parent.host : parent;
4919
+ }
4920
+ return void 0;
4921
+ }
4922
+
5225
4923
  // src/widgets/renderHealth.ts
5226
4924
  var BLANK_MIN_AREA = 60 * 60;
5227
4925
  function elementPath(el, stopAt) {
5228
- const seg = (e2) => {
5229
- const tag = e2.tagName.toLowerCase();
5230
- const cls = typeof e2.className === "string" && e2.className.trim() ? `.${e2.className.trim().split(/\s+/)[0]}` : "";
4926
+ const seg = (e) => {
4927
+ const tag = e.tagName.toLowerCase();
4928
+ const cls = typeof e.className === "string" && e.className.trim() ? `.${e.className.trim().split(/\s+/)[0]}` : "";
5231
4929
  return `${tag}${cls}`;
5232
4930
  };
5233
4931
  const parts = [seg(el)];
@@ -5276,7 +4974,7 @@ function measureRenderHealth(card) {
5276
4974
  const r = el.getBoundingClientRect();
5277
4975
  if (r.width === 0 || r.height === 0) continue;
5278
4976
  const ownText = Array.from(el.childNodes).some(
5279
- (n2) => n2.nodeType === 3 && (n2.textContent ?? "").trim().length > 0
4977
+ (n) => n.nodeType === 3 && (n.textContent ?? "").trim().length > 0
5280
4978
  );
5281
4979
  if (ownText) hasText = true;
5282
4980
  if (el.tagName === "IMG") {
@@ -5310,8 +5008,8 @@ function measureRenderHealth(card) {
5310
5008
  Math.round(ar.top - r.top)
5311
5009
  );
5312
5010
  if (esc > 1 && !hasClipOkAncestor(el, card) && !hasClipOkAncestor(a, card)) {
5313
- const t2 = (el.textContent ?? "").trim();
5314
- if (t2 || ["IMG", "BUTTON", "A"].includes(el.tagName)) {
5011
+ const t = (el.textContent ?? "").trim();
5012
+ if (t || ["IMG", "BUTTON", "A"].includes(el.tagName)) {
5315
5013
  push({ path: elementPath(el, card), over_px: esc });
5316
5014
  }
5317
5015
  }
@@ -5345,9 +5043,9 @@ function reportRenderHealth(root, opts) {
5345
5043
  synEmit(`app:${opts.category}:blank`, base);
5346
5044
  return health;
5347
5045
  }
5348
- const paths = health.clips.map((c2) => c2.path).sort();
5349
- const over_px = health.clips.reduce((m, c2) => Math.max(m, c2.over_px), 0);
5350
- const identityTruncated = health.clips.some((c2) => c2.reason === "identity-truncated");
5046
+ const paths = health.clips.map((c) => c.path).sort();
5047
+ const over_px = health.clips.reduce((m, c) => Math.max(m, c.over_px), 0);
5048
+ const identityTruncated = health.clips.some((c) => c.reason === "identity-truncated");
5351
5049
  synEmit(`app:${opts.category}:clipped`, {
5352
5050
  paths: paths.join(","),
5353
5051
  over_px,
@@ -5648,6 +5346,12 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5648
5346
  __privateAdd(this, _VariantPanelLit_instances);
5649
5347
  this.payload = null;
5650
5348
  this.productTitle = "";
5349
+ /** Set by a double-wide card presentation: the panel sits BESIDE the front
5350
+ * face rather than flipped to. There is no Back — the front already shows
5351
+ * the identity, so the panel's own title element is skipped too (the
5352
+ * "Showing options for …" live-region status line still speaks it, so
5353
+ * screen reader users get the same announcement either way). */
5354
+ this.pinned = false;
5651
5355
  /** Product-level hero image, passed from the card. Shown on the back face and
5652
5356
  * used as the fallback when the selected variant carries no featured_image. */
5653
5357
  this.productImage = null;
@@ -5671,8 +5375,8 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5671
5375
  this._expandedAxes = /* @__PURE__ */ new Set();
5672
5376
  /** @internal — Tier-C per-axis filter text. */
5673
5377
  this._axisFilter = {};
5674
- this._onKeydown = (e2) => {
5675
- if (e2.key === "Escape") {
5378
+ this._onKeydown = (e) => {
5379
+ if (e.key === "Escape") {
5676
5380
  this.dispatchEvent(new CustomEvent("variant-panel-back", { bubbles: true, composed: true }));
5677
5381
  }
5678
5382
  };
@@ -5794,7 +5498,7 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5794
5498
  />` : nothing3}
5795
5499
  </div>
5796
5500
  <div class="svp-body" data-panel-body>
5797
- ${this.productTitle ? html8`<div class="svp-product-title" data-product-title data-critical-text>${this.productTitle}</div>` : nothing3}
5501
+ ${this.productTitle && !this.pinned ? html8`<div class="svp-product-title" data-product-title data-critical-text>${this.productTitle}</div>` : nothing3}
5798
5502
  <div class="svp-attributes-scroll" data-attributes-scroll data-clip-ok>
5799
5503
  ${singleVariant ? html8`<div class="svp-single-variant-note" data-single-variant-note>
5800
5504
  Just one option — ready to add.
@@ -5813,7 +5517,7 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5813
5517
  // so Back fits beside it on the same row).
5814
5518
  ctaPrice != null ? html8`Add ${compareAt ? html8`<span class="svp-compare">$${compareAt.toFixed(2)}</span>` : nothing3}· $${ctaPrice.toFixed(2)}` : html8`Add to cart`}
5815
5519
  </button>
5816
- ${backBtn}
5520
+ ${this.pinned ? nothing3 : backBtn}
5817
5521
  </div>
5818
5522
  <div class="svp-live" data-live data-clip-ok aria-live="polite">
5819
5523
  ${this.productTitle ? `Showing options for ${this.productTitle}` : nothing3}
@@ -5907,8 +5611,8 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5907
5611
  )?.focus();
5908
5612
  });
5909
5613
  }
5910
- _onAxisFilterInput(attrKey, e2) {
5911
- this._axisFilter = { ...this._axisFilter, [attrKey]: e2.target.value };
5614
+ _onAxisFilterInput(attrKey, e) {
5615
+ this._axisFilter = { ...this._axisFilter, [attrKey]: e.target.value };
5912
5616
  }
5913
5617
  /** Density Tier C — select-style trigger + bounded scrollable listbox.
5914
5618
  * ONLY this axis collapses; siblings keep their own tier. Filter appears
@@ -5926,9 +5630,9 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5926
5630
  class="svp-collapse"
5927
5631
  data-collapsed-axis
5928
5632
  data-attribute-key=${attr.key}
5929
- @keydown=${(e2) => {
5930
- if (e2.key === "Escape" && expanded) {
5931
- e2.stopPropagation();
5633
+ @keydown=${(e) => {
5634
+ if (e.key === "Escape" && expanded) {
5635
+ e.stopPropagation();
5932
5636
  this._toggleAxis(attr.key);
5933
5637
  void this.updateComplete.then(() => {
5934
5638
  this.querySelector(
@@ -5965,7 +5669,7 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5965
5669
  type="text"
5966
5670
  placeholder=${`Filter ${attr.label.toLowerCase()}\u2026`}
5967
5671
  .value=${this._axisFilter[attr.key] ?? ""}
5968
- @input=${(e2) => this._onAxisFilterInput(attr.key, e2)}
5672
+ @input=${(e) => this._onAxisFilterInput(attr.key, e)}
5969
5673
  />` : nothing3}
5970
5674
  ${filtered.map((v) => {
5971
5675
  const disabled2 = this._isValueDisabled(attr.key, v.id);
@@ -6007,6 +5711,7 @@ _VariantPanelLit.properties = {
6007
5711
  state: { type: String },
6008
5712
  error: { type: String },
6009
5713
  retryable: { type: Boolean },
5714
+ pinned: { type: Boolean },
6010
5715
  _selection: { state: true },
6011
5716
  _touched: { state: true },
6012
5717
  _expandedAxes: { state: true },
@@ -6127,7 +5832,7 @@ var CARD_FLIP_CSS = `
6127
5832
  .sc-product-card [data-card-flip] { animation: none; }
6128
5833
  }
6129
5834
  `;
6130
- var _effectExecutor, _controller, _unsubDevice, _lastFailureFingerprint, _reportedHealthStates, _healthRaf, _ProductCardLit_instances, scheduleHealthCheck_fn, measureAndReportHealth_fn, cleanupHostResources_fn, dispatchCard_fn, _onFlipAnimationEnd, startBind_fn, readProductObservation_fn, syncDecisionInputsBeforeIntent_fn;
5835
+ var _effectExecutor, _controller, _unsubDevice, _double, _lastFailureFingerprint, _reportedHealthStates, _healthRaf, _ProductCardLit_instances, scheduleHealthCheck_fn, measureAndReportHealth_fn, cleanupHostResources_fn, dispatchCard_fn, _onFlipAnimationEnd, startBind_fn, renderVariantPanel_fn, readProductObservation_fn, syncDecisionInputsBeforeIntent_fn;
6131
5836
  var ProductCardLit = class extends LitElement8 {
6132
5837
  constructor() {
6133
5838
  super(...arguments);
@@ -6151,6 +5856,12 @@ var ProductCardLit = class extends LitElement8 {
6151
5856
  }));
6152
5857
  __privateAdd(this, _controller);
6153
5858
  __privateAdd(this, _unsubDevice);
5859
+ /** Set once at connect from the canvas DOM around the card
5860
+ * (`data-product-presentation="double"`) — never from `props`, so the
5861
+ * agent that writes card props has no path to setting it. Drives the
5862
+ * double-wide render branch: front face and options side by side, options
5863
+ * pinned open. */
5864
+ __privateAdd(this, _double, false);
6154
5865
  /** Fingerprint of the last reported schema-validation failure (sorted
6155
5866
  * invalid paths). Guards `render_failed` telemetry against re-render spam:
6156
5867
  * the same bad props re-rendering must not re-emit. Reset on a successful
@@ -6164,25 +5875,25 @@ var ProductCardLit = class extends LitElement8 {
6164
5875
  * the next scheduled pass or on disconnect. */
6165
5876
  __privateAdd(this, _healthRaf);
6166
5877
  /** animationend on the flip container → the turn is done, render flat. */
6167
- __privateAdd(this, _onFlipAnimationEnd, (e2) => {
6168
- if (e2.animationName === "sc-card-flip-in") __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "animation-settled" });
5878
+ __privateAdd(this, _onFlipAnimationEnd, (e) => {
5879
+ if (e.animationName === "sc-card-flip-in") __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "animation-settled" });
6169
5880
  });
6170
5881
  this._onVariantBack = () => {
6171
5882
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6172
5883
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "back-activated" });
6173
5884
  };
6174
- this._onVariantSelectionChange = (e2) => {
5885
+ this._onVariantSelectionChange = (e) => {
6175
5886
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6176
- const detail = e2.detail;
5887
+ const detail = e.detail;
6177
5888
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, {
6178
5889
  type: "selection-changed",
6179
5890
  attributeKey: detail.attribute_key,
6180
5891
  valueId: detail.value_id
6181
5892
  });
6182
5893
  };
6183
- this._onVariantAdd = (e2) => {
5894
+ this._onVariantAdd = (e) => {
6184
5895
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6185
- const detail = e2.detail;
5896
+ const detail = e.detail;
6186
5897
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "variant-add-activated", variantId: detail.variant.id });
6187
5898
  };
6188
5899
  this._onVariantRetry = () => {
@@ -6195,6 +5906,8 @@ var ProductCardLit = class extends LitElement8 {
6195
5906
  }
6196
5907
  connectedCallback() {
6197
5908
  super.connectedCallback();
5909
+ __privateSet(this, _double, presentationFromContext(this) === "double");
5910
+ if (__privateGet(this, _double)) __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "options-pinned", pinned: true });
6198
5911
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "reconnected" });
6199
5912
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, {
6200
5913
  type: "commerce-updated",
@@ -6204,8 +5917,8 @@ var ProductCardLit = class extends LitElement8 {
6204
5917
  this.style.color = "var(--sc-tile-text-color, rgba(20, 20, 20, 0.92))";
6205
5918
  this._device = _readDevice();
6206
5919
  const ctx = synWindow().SynOS?.runtime?.context;
6207
- __privateSet(this, _unsubDevice, ctx?.subscribe?.((c2) => {
6208
- const d = c2?.surface?.device;
5920
+ __privateSet(this, _unsubDevice, ctx?.subscribe?.((c) => {
5921
+ const d = c?.surface?.device;
6209
5922
  const next = d === "mobile" || d === "tablet" ? d : "desktop";
6210
5923
  if (next !== this._device) this._device = next;
6211
5924
  }));
@@ -6238,18 +5951,37 @@ var ProductCardLit = class extends LitElement8 {
6238
5951
  const resolved = this.resolved[product.id];
6239
5952
  const view = selectCardView(this._decisionState);
6240
5953
  const articleStyles = _buildArticleStyles(density, view.face);
6241
- const onPrimaryCtaClick = (e2) => {
6242
- e2.preventDefault();
5954
+ const onPrimaryCtaClick = (e) => {
5955
+ e.preventDefault();
6243
5956
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6244
5957
  const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
6245
5958
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "primary-activated", reducedMotion });
6246
5959
  };
6247
- const ctasWithMorphedLabel = product.ctas.map((c2, i2) => {
6248
- if (i2 === 0 && view.primaryCta.mode === "view-cart") {
6249
- return { ...c2, label: "View cart \u2192" };
5960
+ const ctasWithMorphedLabel = product.ctas.map((c, i) => {
5961
+ if (i === 0 && view.primaryCta.mode === "view-cart") {
5962
+ return { ...c, label: "View cart \u2192" };
6250
5963
  }
6251
- return c2;
5964
+ return c;
6252
5965
  });
5966
+ if (__privateGet(this, _double)) {
5967
+ return html9`<article
5968
+ class="sc-product-card"
5969
+ data-presentation="double"
5970
+ data-density=${density}
5971
+ style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:16px;align-items:start;"
5972
+ >
5973
+ <div data-face="front">
5974
+ ${renderProductCardInner(product, density, resolved, visibleFacts, [], void 0, true)}
5975
+ </div>
5976
+ <div data-face="back">
5977
+ ${__privateMethod(this, _ProductCardLit_instances, renderVariantPanel_fn).call(this, view, product, {
5978
+ pinned: true,
5979
+ productImage: null,
5980
+ wireBack: false
5981
+ })}
5982
+ </div>
5983
+ </article>`;
5984
+ }
6253
5985
  const frontContent = renderProductCardInner(
6254
5986
  product,
6255
5987
  density,
@@ -6259,19 +5991,11 @@ var ProductCardLit = class extends LitElement8 {
6259
5991
  onPrimaryCtaClick,
6260
5992
  !view.primaryCta.disabled
6261
5993
  );
6262
- const backFace = html9`<syntro-variant-panel
6263
- .payload=${view.panel.payload}
6264
- .productTitle=${product.name ?? ""}
6265
- .productImage=${product.image ? { src: product.image.src, alt: product.image.alt } : null}
6266
- .state=${view.panel.status === "error" ? "error" : view.panel.status === "ready" ? "idle" : "loading"}
6267
- .error=${view.panel.error}
6268
- .retryable=${view.panel.retryable}
6269
- @variant-panel-back=${this._onVariantBack}
6270
- @variant-panel-add=${this._onVariantAdd}
6271
- @variant-panel-retry=${this._onVariantRetry}
6272
- @variant-panel-selection-change=${this._onVariantSelectionChange}
6273
- ></syntro-variant-panel>
6274
- ${view.addError ? html9`<div data-add-to-cart-error>${view.addError}</div>` : nothing4}`;
5994
+ const backFace = __privateMethod(this, _ProductCardLit_instances, renderVariantPanel_fn).call(this, view, product, {
5995
+ pinned: false,
5996
+ productImage: product.image ? { src: product.image.src, alt: product.image.alt } : null,
5997
+ wireBack: true
5998
+ });
6275
5999
  const backView = view.face === "back" && view.flipAnimating ? (
6276
6000
  // Mid-flip: both faces coexist inside a preserve-3d container. The
6277
6001
  // front turns away (inert + aria-hidden) while the back turns in.
@@ -6302,6 +6026,7 @@ var ProductCardLit = class extends LitElement8 {
6302
6026
  _effectExecutor = new WeakMap();
6303
6027
  _controller = new WeakMap();
6304
6028
  _unsubDevice = new WeakMap();
6029
+ _double = new WeakMap();
6305
6030
  _lastFailureFingerprint = new WeakMap();
6306
6031
  _reportedHealthStates = new WeakMap();
6307
6032
  _healthRaf = new WeakMap();
@@ -6366,6 +6091,7 @@ dispatchCard_fn = function(event) {
6366
6091
  _onFlipAnimationEnd = new WeakMap();
6367
6092
  startBind_fn = function() {
6368
6093
  __privateGet(this, _controller)?.destroy();
6094
+ const previous = this.resolved;
6369
6095
  this.resolved = {};
6370
6096
  if (!this.props) {
6371
6097
  this.validationError = false;
@@ -6392,15 +6118,41 @@ startBind_fn = function() {
6392
6118
  __privateSet(this, _lastFailureFingerprint, null);
6393
6119
  this.validationError = false;
6394
6120
  const parsed = result.data;
6121
+ this.resolved = keepResolved(previous, [parsed.product]);
6395
6122
  __privateSet(this, _controller, new BindController([parsed.product], (productId, kind, value) => {
6396
- if (value == null) return;
6397
- this.resolved = {
6398
- ...this.resolved,
6399
- [productId]: { ...this.resolved[productId], [kind]: value }
6400
- };
6123
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
6401
6124
  }));
6402
6125
  __privateGet(this, _controller).start();
6403
6126
  };
6127
+ /**
6128
+ * The `<syntro-variant-panel>` markup, shared by the flip card's back face
6129
+ * and the double-wide presentation's side panel — everything (payload,
6130
+ * title, state, error, retryable, the add/retry/selection-change handlers,
6131
+ * and the addError block) is identical between the two callers. Only three
6132
+ * things differ, so they're the only knobs: `pinned` (double-wide has no
6133
+ * Back and no title element — see VariantPanelLit), `productImage` (the
6134
+ * flip back face shows the product's hero image; the double-wide panel
6135
+ * shows none, since the front face right beside it already carries the
6136
+ * image), and `wireBack` (the double-wide panel has no Back control to
6137
+ * fire it and the card never flips there, so wiring the handler would be
6138
+ * live but pointless — omitted to match the panel's own no-Back contract).
6139
+ */
6140
+ renderVariantPanel_fn = function(view, product, options) {
6141
+ return html9`<syntro-variant-panel
6142
+ ?pinned=${options.pinned}
6143
+ .payload=${view.panel.payload}
6144
+ .productTitle=${product.name ?? ""}
6145
+ .productImage=${options.productImage}
6146
+ .state=${view.panel.status === "error" ? "error" : view.panel.status === "ready" ? "idle" : "loading"}
6147
+ .error=${view.panel.error}
6148
+ .retryable=${view.panel.retryable}
6149
+ @variant-panel-back=${options.wireBack ? this._onVariantBack : void 0}
6150
+ @variant-panel-add=${this._onVariantAdd}
6151
+ @variant-panel-retry=${this._onVariantRetry}
6152
+ @variant-panel-selection-change=${this._onVariantSelectionChange}
6153
+ ></syntro-variant-panel>
6154
+ ${view.addError ? html9`<div data-add-to-cart-error>${view.addError}</div>` : nothing4}`;
6155
+ };
6404
6156
  readProductObservation_fn = function() {
6405
6157
  const parsed = this.props ? cardSchema.safeParse(this.props) : null;
6406
6158
  const rawProductId = this.props?.product?.id;
@@ -6527,7 +6279,9 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6527
6279
  const factOrder = Array.from(
6528
6280
  /* @__PURE__ */ new Set([...CORE_FACTS, ...visibleFacts ?? VISIBLE_FACT_KEYS])
6529
6281
  );
6530
- const hasSalePrice = product.salePrice !== void 0;
6282
+ const storeAnswered = resolved !== void 0 && "compareAt" in resolved;
6283
+ const hasSalePrice = storeAnswered ? Boolean(resolved?.compareAt) : product.salePrice !== void 0;
6284
+ const originalPriceText = storeAnswered ? resolved?.compareAt : product.price?.amount;
6531
6285
  const activePriceText = resolved?.price ?? product.salePrice?.amount ?? product.price?.amount;
6532
6286
  const activeCadence = hasSalePrice ? product.salePrice?.cadence : product.price?.cadence;
6533
6287
  const overlay = density === "compact";
@@ -6830,10 +6584,15 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6830
6584
  borderRadius: "9999px",
6831
6585
  border: "0",
6832
6586
  background: "transparent",
6833
- color: "var(--sc-color-primary, #3d8a5e)",
6587
+ // Accent as ink on `transparent` is ink on the HOST's ground inside a
6588
+ // card, where vela's #723b32 measures 1.3:1. The host's own ink wins
6589
+ // there; the merchant's accent everywhere else.
6590
+ color: "var(--sc-host-link-color, var(--sc-color-primary, #3d8a5e))",
6834
6591
  fontWeight: "600",
6835
6592
  fontSize: "0.82rem",
6836
- textDecoration: "none",
6593
+ // Once the link is the same colour as the text around it, colour alone no
6594
+ // longer says it is a link (WCAG 1.4.1), so the host card underlines it.
6595
+ textDecoration: "var(--sc-host-link-decoration, none)",
6837
6596
  textAlign: "center",
6838
6597
  lineHeight: "1.15",
6839
6598
  // Same terminal policy as the primary pill: shrink + wrap, never overflow.
@@ -6843,41 +6602,41 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6843
6602
  maxWidth: "100%",
6844
6603
  flexShrink: "1"
6845
6604
  };
6846
- const ctaRow = html9`<div class="sc-product-card__ctas" style=${styleMap(ctasStyles)}>
6847
- ${ctas.map((c2, i2) => {
6848
- const disabled2 = c2.actionId === "add_to_cart" && !commerceAvailable;
6605
+ const ctaRow = ctas.length === 0 ? nothing4 : html9`<div class="sc-product-card__ctas" style=${styleMap(ctasStyles)}>
6606
+ ${ctas.map((c, i) => {
6607
+ const disabled2 = c.actionId === "add_to_cart" && !commerceAvailable;
6849
6608
  const ctaStyles = {
6850
- ...i2 === 0 ? primaryCtaStyles : secondaryCtaStyles,
6609
+ ...i === 0 ? primaryCtaStyles : secondaryCtaStyles,
6851
6610
  ...disabled2 ? { opacity: "0.5", "pointer-events": "none", cursor: "not-allowed" } : {}
6852
6611
  };
6853
- const ctaLabel = c2.label;
6612
+ const ctaLabel = c.label;
6854
6613
  return html9`<a
6855
6614
  class="sc-product-card__cta"
6856
- data-product-cta=${i2 === 0 ? "primary" : "secondary"}
6615
+ data-product-cta=${i === 0 ? "primary" : "secondary"}
6857
6616
  data-critical-text
6858
- data-variant=${c2.variant}
6617
+ data-variant=${c.variant}
6859
6618
  data-disabled=${disabled2 ? "true" : nothing4}
6860
6619
  aria-disabled=${disabled2 ? "true" : nothing4}
6861
- href=${c2.actionId === "add_to_cart" ? "#" : c2.href ?? "#"}
6862
- target=${c2.target}
6863
- rel=${c2.target === "_blank" ? "noopener noreferrer" : ""}
6864
- data-action-id=${c2.actionId ?? ""}
6620
+ href=${c.actionId === "add_to_cart" ? "#" : c.href ?? "#"}
6621
+ target=${c.target}
6622
+ rel=${c.target === "_blank" ? "noopener noreferrer" : ""}
6623
+ data-action-id=${c.actionId ?? ""}
6865
6624
  style=${styleMap(ctaStyles)}
6866
- @click=${disabled2 ? (e2) => {
6867
- e2.preventDefault();
6625
+ @click=${disabled2 ? (e) => {
6626
+ e.preventDefault();
6868
6627
  } : (
6869
6628
  // The commerce (Add-to-cart / open-picker) intent wires ONLY to
6870
6629
  // an actual add_to_cart CTA. A plain navigating CTA — e.g. a
6871
6630
  // booking-fulfilment product's "Book a consult" href, which
6872
6631
  // sits at i===0 too — must fall through to its own <a href>
6873
6632
  // and never enter the Woo/Shopify variant-lookup flow.
6874
- i2 === 0 && c2.actionId === "add_to_cart" && onPrimaryCtaClick ? onPrimaryCtaClick : c2.actionId === "add_to_cart" ? (e2) => {
6875
- e2.preventDefault();
6633
+ i === 0 && c.actionId === "add_to_cart" && onPrimaryCtaClick ? onPrimaryCtaClick : c.actionId === "add_to_cart" ? (e) => {
6634
+ e.preventDefault();
6876
6635
  } : void 0
6877
6636
  )}
6878
6637
  >${// Secondary "Product Page" link gets a trailing chevron (panel's
6879
6638
  // quiet-secondary affordance) unless the author already added one.
6880
- i2 !== 0 && !/[›>→]\s*$/.test(ctaLabel) ? `${ctaLabel} \u203A` : ctaLabel}</a
6639
+ i !== 0 && !/[›>→]\s*$/.test(ctaLabel) ? `${ctaLabel} \u203A` : ctaLabel}</a
6881
6640
  >`;
6882
6641
  })}
6883
6642
  </div>`;
@@ -7080,11 +6839,11 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
7080
6839
  data-product-price
7081
6840
  style=${styleMap(priceBlockStyles)}
7082
6841
  >
7083
- ${hasSalePrice && product.price ? html9`<span
6842
+ ${hasSalePrice && originalPriceText ? html9`<span
7084
6843
  class="sc-product-card__amount-original"
7085
6844
  data-product-price-original
7086
6845
  style=${styleMap(priceOriginalStyles)}
7087
- >${product.price.amount}</span
6846
+ >${originalPriceText}</span
7088
6847
  >` : nothing4}
7089
6848
  <span
7090
6849
  class="sc-product-card__amount"
@@ -7246,8 +7005,8 @@ function renderProductCard(product, density, resolved, visibleFacts) {
7246
7005
  </article>
7247
7006
  `;
7248
7007
  }
7249
- function onImageError(e2) {
7250
- const img = e2.currentTarget;
7008
+ function onImageError(e) {
7009
+ const img = e.currentTarget;
7251
7010
  img.dataset.imageError = "true";
7252
7011
  img.removeAttribute("src");
7253
7012
  }
@@ -7422,6 +7181,7 @@ syncScrollCue_fn = function() {
7422
7181
  };
7423
7182
  startBind_fn2 = function() {
7424
7183
  __privateGet(this, _controller2)?.destroy();
7184
+ const previous = this.resolved;
7425
7185
  this.resolved = {};
7426
7186
  if (!this.props) {
7427
7187
  this.validationError = false;
@@ -7437,12 +7197,9 @@ startBind_fn2 = function() {
7437
7197
  }
7438
7198
  this.validationError = false;
7439
7199
  __privateSet(this, _parsed, result.data);
7200
+ this.resolved = keepResolved(previous, result.data.products);
7440
7201
  __privateSet(this, _controller2, new BindController(result.data.products, (productId, kind, value) => {
7441
- if (value == null) return;
7442
- this.resolved = {
7443
- ...this.resolved,
7444
- [productId]: { ...this.resolved[productId], [kind]: value }
7445
- };
7202
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
7446
7203
  }));
7447
7204
  __privateGet(this, _controller2).start();
7448
7205
  };
@@ -7457,7 +7214,7 @@ startBind_fn2 = function() {
7457
7214
  */
7458
7215
  renderDiveDeeper_fn = function(parsed) {
7459
7216
  if (parsed.products.length < 2) return nothing5;
7460
- const chipStyle = "flex:0 0 auto;align-self:flex-end;margin-top:6px;padding:4px 10px;border-radius:9999px;border:1px solid var(--sc-color-border,rgba(0,0,0,0.14));background:transparent;cursor:pointer;font:600 12px/1.2 var(--sc-font-family,inherit);color:var(--sc-color-primary,#3d8a5e);white-space:nowrap;";
7217
+ const chipStyle = "flex:0 0 auto;align-self:flex-end;margin-top:6px;padding:4px 10px;border-radius:9999px;border:1px solid var(--sc-color-border,rgba(0,0,0,0.14));background:transparent;cursor:pointer;font:600 12px/1.2 var(--sc-font-family,inherit);color:var(--sc-host-link-color,var(--sc-color-primary,#3d8a5e));white-space:nowrap;";
7461
7218
  return html10`<button
7462
7219
  type="button"
7463
7220
  class="sc-product-comparison__dive-deeper"
@@ -7502,8 +7259,8 @@ rowComplete_fn = function(row, productCount) {
7502
7259
  maxFields_fn = function() {
7503
7260
  const raw = getComputedStyle(this).getPropertyValue("--sc-comparison-max-fields").trim();
7504
7261
  if (!raw) return null;
7505
- const n2 = Number.parseInt(raw, 10);
7506
- return Number.isFinite(n2) && n2 > 0 ? n2 : null;
7262
+ const n = Number.parseInt(raw, 10);
7263
+ return Number.isFinite(n) && n > 0 ? n : null;
7507
7264
  };
7508
7265
  /**
7509
7266
  * Build comparison rows from the products' own `attributes` when the assistant
@@ -7520,10 +7277,10 @@ synthesizeRows_fn = function(products) {
7520
7277
  }
7521
7278
  }
7522
7279
  return labels.map((label) => {
7523
- const [a, b, c2, d] = products.map(
7280
+ const [a, b, c, d] = products.map(
7524
7281
  (p) => (p.attributes ?? []).find((x) => x.label === label)?.value ?? "\u2014"
7525
7282
  );
7526
- return { label, valueA: a ?? "\u2014", valueB: b ?? "\u2014", valueC: c2, valueD: d };
7283
+ return { label, valueA: a ?? "\u2014", valueB: b ?? "\u2014", valueC: c, valueD: d };
7527
7284
  });
7528
7285
  };
7529
7286
  /**
@@ -7535,8 +7292,8 @@ renderMatrix_fn = function(products, rows) {
7535
7292
  const cols = `minmax(62px, 0.55fr) repeat(${products.length}, minmax(0, 1fr))`;
7536
7293
  const compactHeaders = products.length >= 3;
7537
7294
  const colHeadStyle = "display:block;padding:6px 3px;text-align:left;font-weight:600;min-width:0;";
7538
- const labelStyle = "padding:8px 4px 8px 2px;font-weight:600;font-size:0.75rem;opacity:0.75;border-top:1px solid var(--sc-tile-border-color,rgba(0,0,0,0.08));align-self:stretch;min-width:0;overflow-wrap:break-word;hyphens:auto;";
7539
- const cellStyle = "padding:8px 6px;border-top:1px solid var(--sc-tile-border-color,rgba(0,0,0,0.08));align-self:stretch;min-width:0;overflow-wrap:anywhere;";
7295
+ const labelStyle = "padding:8px 4px 8px 2px;font-weight:600;font-size:0.75rem;opacity:0.75;border-top:1px solid var(--sc-content-divider-color,rgba(0,0,0,0.08));align-self:stretch;min-width:0;overflow-wrap:break-word;hyphens:auto;";
7296
+ const cellStyle = "padding:8px 6px;border-top:1px solid var(--sc-content-divider-color,rgba(0,0,0,0.08));align-self:stretch;min-width:0;overflow-wrap:anywhere;";
7540
7297
  return html10`
7541
7298
  <div
7542
7299
  class="sc-product-comparison__matrix"
@@ -7638,8 +7395,8 @@ renderMatrix_fn = function(products, rows) {
7638
7395
  >${row.label}</span
7639
7396
  >
7640
7397
  ${products.map(
7641
- (_p, i2) => html10`<span class="sc-product-comparison__matrix-cell" role="cell" style=${cellStyle}
7642
- >${cells[i2] ?? "\u2014"}</span
7398
+ (_p, i) => html10`<span class="sc-product-comparison__matrix-cell" role="cell" style=${cellStyle}
7399
+ >${cells[i] ?? "\u2014"}</span
7643
7400
  >`
7644
7401
  )}
7645
7402
  </div>
@@ -7655,10 +7412,167 @@ ProductComparisonLit.properties = {
7655
7412
  scrollFade: { state: true }
7656
7413
  };
7657
7414
 
7658
- // src/widgets/ProductGridLit.ts
7415
+ // src/widgets/ProductDetailsLit.ts
7659
7416
  import { html as html11, LitElement as LitElement10, nothing as nothing6 } from "lit";
7417
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
7418
+ var EMPTY_MARKER = html11`<span data-sc-empty hidden></span>`;
7419
+ var DETAILS_CSS = `
7420
+ .pd {
7421
+ max-width: 1040px;
7422
+ margin: 0 auto 16px;
7423
+ display: grid;
7424
+ gap: 16px;
7425
+ }
7426
+ .pd-lead p {
7427
+ margin: 0 0 6px;
7428
+ }
7429
+ .pd-eyebrow {
7430
+ font-size: 0.75rem;
7431
+ letter-spacing: 0.06em;
7432
+ text-transform: uppercase;
7433
+ opacity: 0.75;
7434
+ }
7435
+ .pd-badge {
7436
+ margin-left: 6px;
7437
+ padding: 1px 8px;
7438
+ border-radius: 9999px;
7439
+ box-shadow: inset 0 0 0 1px currentColor;
7440
+ text-transform: none;
7441
+ letter-spacing: 0;
7442
+ }
7443
+ .pd-rating {
7444
+ font-size: 0.875rem;
7445
+ }
7446
+ .pd-desc {
7447
+ line-height: 1.5;
7448
+ max-width: 70ch;
7449
+ }
7450
+ .pd-benefits {
7451
+ margin: 4px 0 0;
7452
+ padding-left: 18px;
7453
+ }
7454
+ .pd-cols {
7455
+ display: grid;
7456
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
7457
+ gap: 16px;
7458
+ padding: 14px 0;
7459
+ border-top: 1px solid color-mix(in srgb, currentColor 18%, transparent);
7460
+ border-bottom: 1px solid color-mix(in srgb, currentColor 18%, transparent);
7461
+ font-size: 0.875rem;
7462
+ }
7463
+ .pd-cols h3 {
7464
+ margin: 0 0 6px;
7465
+ font-size: 0.8125rem;
7466
+ letter-spacing: 0.04em;
7467
+ text-transform: uppercase;
7468
+ opacity: 0.8;
7469
+ }
7470
+ .pd-cols p,
7471
+ .pd-cols ul {
7472
+ margin: 0 0 4px;
7473
+ }
7474
+ .pd-cols ul {
7475
+ padding-left: 18px;
7476
+ }
7477
+ `;
7478
+ var ProductDetailsLit = class extends LitElement10 {
7479
+ constructor() {
7480
+ super(...arguments);
7481
+ this._productId = "";
7482
+ this._details = null;
7483
+ }
7484
+ createRenderRoot() {
7485
+ return this;
7486
+ }
7487
+ connectedCallback() {
7488
+ super.connectedCallback();
7489
+ ensureStylesInRoot(this, "syntro-product-details-style", DETAILS_CSS);
7490
+ }
7491
+ /**
7492
+ * Widget props (the `adaptive-product:details` mountable sets `el.props`
7493
+ * before appending). Starts the details fetch immediately — reading
7494
+ * `productId` off the adapter, not off any attribute — and keys the
7495
+ * in-flight request by productId so a stale answer arriving after a later
7496
+ * `props` set (e.g. the card swapped to a different product) is dropped.
7497
+ */
7498
+ set props(value) {
7499
+ const id = value?.productId ?? "";
7500
+ this._productId = id;
7501
+ this._details = null;
7502
+ if (!id) return;
7503
+ const adapter = getProductPlatformAdapter();
7504
+ if (typeof adapter?.fetchProductDetails !== "function") return;
7505
+ adapter.fetchProductDetails(id).then((details) => {
7506
+ if (this._productId !== id) return;
7507
+ this._details = details;
7508
+ }).catch(() => {
7509
+ });
7510
+ }
7511
+ get props() {
7512
+ return this._productId ? { productId: this._productId } : void 0;
7513
+ }
7514
+ render() {
7515
+ const details = this._details;
7516
+ if (!details) return EMPTY_MARKER;
7517
+ const d = details.details;
7518
+ const eyebrow = [d?.category, d?.gender].filter(Boolean).join(" \xB7 ");
7519
+ const badges = d?.badges ?? [];
7520
+ const benefits = d?.benefits ?? [];
7521
+ const moreBenefits = benefits.slice(3);
7522
+ const hasFabric = Boolean(d?.fabric || moreBenefits.length);
7523
+ const hasCols = Boolean(
7524
+ d && (hasFabric || (d.care?.length ?? 0) > 0 || d.shipping || d.returns)
7525
+ );
7526
+ const hasLead = Boolean(
7527
+ eyebrow || badges.length || details.rating || details.description || benefits.length
7528
+ );
7529
+ if (!hasLead && !hasCols) return EMPTY_MARKER;
7530
+ return html11`
7531
+ <section class="pd" data-product-details aria-label="Product details">
7532
+ <div class="pd-lead">
7533
+ ${eyebrow || badges.length ? html11`<p class="pd-eyebrow">
7534
+ ${eyebrow}${badges.map((b) => html11` <span class="pd-badge">${b}</span>`)}
7535
+ </p>` : nothing6}
7536
+ ${details.rating ? html11`<p class="pd-rating">
7537
+ ★ ${details.rating.value} · ${details.rating.count.toLocaleString("en-US")} reviews
7538
+ </p>` : nothing6}
7539
+ ${details.description ? html11`<div class="pd-desc">${unsafeHTML(sanitizeProductDescription(details.description))}</div>` : nothing6}
7540
+ ${benefits.length ? html11`<ul class="pd-benefits">
7541
+ ${benefits.slice(0, 3).map((b) => html11`<li>${b}</li>`)}
7542
+ </ul>` : nothing6}
7543
+ </div>
7544
+ ${hasCols ? html11`<div class="pd-cols">
7545
+ ${hasFabric ? html11`<div>
7546
+ <h3>Fabric &amp; benefits</h3>
7547
+ ${d?.fabric ? html11`<p>${d.fabric}</p>` : nothing6}
7548
+ ${moreBenefits.length ? html11`<ul>
7549
+ ${moreBenefits.map((b) => html11`<li>${b}</li>`)}
7550
+ </ul>` : nothing6}
7551
+ </div>` : nothing6}
7552
+ <div>
7553
+ <h3>Shipping &amp; returns</h3>
7554
+ ${d?.shipping ? html11`<p>${d.shipping}</p>` : nothing6}
7555
+ ${d?.returns ? html11`<p>${d.returns}</p>` : nothing6}
7556
+ </div>
7557
+ <div>
7558
+ <h3>Care</h3>
7559
+ ${d?.care?.length ? html11`<ul>
7560
+ ${d.care.map((c) => html11`<li>${c}</li>`)}
7561
+ </ul>` : nothing6}
7562
+ </div>
7563
+ </div>` : nothing6}
7564
+ </section>
7565
+ `;
7566
+ }
7567
+ };
7568
+ ProductDetailsLit.properties = {
7569
+ _details: { state: true }
7570
+ };
7571
+
7572
+ // src/widgets/ProductGridLit.ts
7573
+ import { html as html12, LitElement as LitElement11, nothing as nothing7 } from "lit";
7660
7574
  var _controller3, _ProductGridLit_instances, startBind_fn3;
7661
- var ProductGridLit = class extends LitElement10 {
7575
+ var ProductGridLit = class extends LitElement11 {
7662
7576
  constructor() {
7663
7577
  super(...arguments);
7664
7578
  __privateAdd(this, _ProductGridLit_instances);
@@ -7684,20 +7598,20 @@ var ProductGridLit = class extends LitElement10 {
7684
7598
  __privateGet(this, _controller3)?.destroy();
7685
7599
  }
7686
7600
  render() {
7687
- if (this.validationError || !this.props) return html11``;
7601
+ if (this.validationError || !this.props) return html12``;
7688
7602
  const parsed = gridSchema.safeParse(this.props);
7689
- if (!parsed.success) return html11``;
7603
+ if (!parsed.success) return html12``;
7690
7604
  const { products, desktopColumns, heading } = parsed.data;
7691
- return html11`
7605
+ return html12`
7692
7606
  <section class="sc-product-grid">
7693
- ${heading ? html11`<h2 data-grid-heading class="sc-product-grid__heading">${heading}</h2>` : nothing6}
7607
+ ${heading ? html12`<h2 data-grid-heading class="sc-product-grid__heading">${heading}</h2>` : nothing7}
7694
7608
  <div
7695
7609
  data-grid-root
7696
7610
  class="sc-product-grid__cells"
7697
7611
  style=${`--sc-product-grid-cols:${desktopColumns}`}
7698
7612
  >
7699
7613
  ${products.map(
7700
- (p) => html11`<div data-grid-cell class="sc-product-grid__cell">
7614
+ (p) => html12`<div data-grid-cell class="sc-product-grid__cell">
7701
7615
  ${renderProductCard(p, "standard", this.resolved[p.id])}
7702
7616
  </div>`
7703
7617
  )}
@@ -7710,6 +7624,7 @@ _controller3 = new WeakMap();
7710
7624
  _ProductGridLit_instances = new WeakSet();
7711
7625
  startBind_fn3 = function() {
7712
7626
  __privateGet(this, _controller3)?.destroy();
7627
+ const previous = this.resolved;
7713
7628
  this.resolved = {};
7714
7629
  if (!this.props) {
7715
7630
  this.validationError = false;
@@ -7722,12 +7637,9 @@ startBind_fn3 = function() {
7722
7637
  return;
7723
7638
  }
7724
7639
  this.validationError = false;
7640
+ this.resolved = keepResolved(previous, result.data.products);
7725
7641
  __privateSet(this, _controller3, new BindController(result.data.products, (productId, kind, value) => {
7726
- if (value == null) return;
7727
- this.resolved = {
7728
- ...this.resolved,
7729
- [productId]: { ...this.resolved[productId], [kind]: value }
7730
- };
7642
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
7731
7643
  }));
7732
7644
  __privateGet(this, _controller3).start();
7733
7645
  };
@@ -7738,9 +7650,9 @@ ProductGridLit.properties = {
7738
7650
  };
7739
7651
 
7740
7652
  // src/widgets/ProductHeroLit.ts
7741
- import { html as html12, LitElement as LitElement11, nothing as nothing7 } from "lit";
7653
+ import { html as html13, LitElement as LitElement12, nothing as nothing8 } from "lit";
7742
7654
  var _controller4, _ProductHeroLit_instances, startBind_fn4;
7743
- var ProductHeroLit = class extends LitElement11 {
7655
+ var ProductHeroLit = class extends LitElement12 {
7744
7656
  constructor() {
7745
7657
  super(...arguments);
7746
7658
  __privateAdd(this, _ProductHeroLit_instances);
@@ -7766,9 +7678,9 @@ var ProductHeroLit = class extends LitElement11 {
7766
7678
  __privateGet(this, _controller4)?.destroy();
7767
7679
  }
7768
7680
  render() {
7769
- if (this.validationError || !this.props) return html12``;
7681
+ if (this.validationError || !this.props) return html13``;
7770
7682
  const parsed = heroSchema.safeParse(this.props);
7771
- if (!parsed.success) return html12``;
7683
+ if (!parsed.success) return html13``;
7772
7684
  const { product, layout, longCopy } = parsed.data;
7773
7685
  const resolved = this.resolved[product.id];
7774
7686
  const imgSrc = resolved?.image ?? product.image?.src;
@@ -7777,15 +7689,15 @@ var ProductHeroLit = class extends LitElement11 {
7777
7689
  const sectionStyles = isTopLayout ? "display:block;" : `display:flex;flex-direction:${layout === "image-right" ? "row-reverse" : "row"};gap:1rem;align-items:flex-start;`;
7778
7690
  const imageStyles = isTopLayout ? "width:100%;max-height:160px;object-fit:cover;border-radius:8px;display:block;margin-bottom:0.75rem;" : "width:128px;height:128px;object-fit:cover;border-radius:8px;flex-shrink:0;";
7779
7691
  const contentStyles = isTopLayout ? "" : "flex:1;min-width:0;";
7780
- return html12`
7692
+ return html13`
7781
7693
  <section class="sc-product-hero" data-hero-layout=${layout} style=${sectionStyles}>
7782
- ${imgSrc ? html12`<img
7694
+ ${imgSrc ? html13`<img
7783
7695
  class="sc-product-hero__image"
7784
7696
  src=${imgSrc}
7785
7697
  alt=${product.image?.alt ?? product.name}
7786
7698
  loading="lazy"
7787
7699
  style=${imageStyles}
7788
- />` : html12`<div
7700
+ />` : html13`<div
7789
7701
  class="sc-product-hero__image"
7790
7702
  data-product-image-standin
7791
7703
  aria-hidden="true"
@@ -7793,25 +7705,25 @@ var ProductHeroLit = class extends LitElement11 {
7793
7705
  >${standinMonogram(product.name)}</div>`}
7794
7706
  <div class="sc-product-hero__content" style=${contentStyles}>
7795
7707
  <h2 class="sc-product-hero__name">${product.name}</h2>
7796
- ${product.tagline ? html12`<p class="sc-product-hero__tagline">${product.tagline}</p>` : nothing7}
7797
- ${product.price ? html12`<p class="sc-product-hero__price">
7798
- ${priceText}${product.price.cadence ? html12` <small>${product.price.cadence}</small>` : nothing7}
7799
- </p>` : nothing7}
7800
- ${longCopy ? html12`<div
7708
+ ${product.tagline ? html13`<p class="sc-product-hero__tagline">${product.tagline}</p>` : nothing8}
7709
+ ${product.price ? html13`<p class="sc-product-hero__price">
7710
+ ${priceText}${product.price.cadence ? html13` <small>${product.price.cadence}</small>` : nothing8}
7711
+ </p>` : nothing8}
7712
+ ${longCopy ? html13`<div
7801
7713
  data-hero-longcopy
7802
7714
  class="sc-product-hero__longcopy"
7803
7715
  .innerHTML=${sanitizeHtml(longCopy)}
7804
- ></div>` : nothing7}
7716
+ ></div>` : nothing8}
7805
7717
  <div class="sc-product-hero__ctas">
7806
7718
  ${product.ctas.map(
7807
- (c2, i2) => html12`<a
7719
+ (c, i) => html13`<a
7808
7720
  class="sc-product-hero__cta"
7809
- data-product-cta=${i2 === 0 ? "primary" : "secondary"}
7810
- data-variant=${c2.variant}
7811
- href=${c2.href ?? "#"}
7812
- target=${c2.target}
7813
- rel=${c2.target === "_blank" ? "noopener noreferrer" : ""}
7814
- >${c2.label}</a
7721
+ data-product-cta=${i === 0 ? "primary" : "secondary"}
7722
+ data-variant=${c.variant}
7723
+ href=${c.href ?? "#"}
7724
+ target=${c.target}
7725
+ rel=${c.target === "_blank" ? "noopener noreferrer" : ""}
7726
+ >${c.label}</a
7815
7727
  >`
7816
7728
  )}
7817
7729
  </div>
@@ -7824,6 +7736,7 @@ _controller4 = new WeakMap();
7824
7736
  _ProductHeroLit_instances = new WeakSet();
7825
7737
  startBind_fn4 = function() {
7826
7738
  __privateGet(this, _controller4)?.destroy();
7739
+ const previous = this.resolved;
7827
7740
  this.resolved = {};
7828
7741
  if (!this.props) {
7829
7742
  this.validationError = false;
@@ -7836,12 +7749,9 @@ startBind_fn4 = function() {
7836
7749
  return;
7837
7750
  }
7838
7751
  this.validationError = false;
7752
+ this.resolved = keepResolved(previous, [result.data.product]);
7839
7753
  __privateSet(this, _controller4, new BindController([result.data.product], (productId, kind, value) => {
7840
- if (value == null) return;
7841
- this.resolved = {
7842
- ...this.resolved,
7843
- [productId]: { ...this.resolved[productId], [kind]: value }
7844
- };
7754
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
7845
7755
  }));
7846
7756
  __privateGet(this, _controller4).start();
7847
7757
  };
@@ -7852,15 +7762,17 @@ ProductHeroLit.properties = {
7852
7762
  };
7853
7763
 
7854
7764
  // src/widgets/ProductRecoCarouselLit.ts
7855
- import { html as html13, LitElement as LitElement12, nothing as nothing8 } from "lit";
7765
+ import { html as html14, LitElement as LitElement13, nothing as nothing9 } from "lit";
7856
7766
  import { styleMap as styleMap2 } from "lit/directives/style-map.js";
7857
- var _ProductRecoCarouselLit_instances, healthProbe_fn3;
7858
- var ProductRecoCarouselLit = class extends LitElement12 {
7767
+ var _unwatch3, _ProductRecoCarouselLit_instances, healthProbe_fn3, watchPrices_fn, stopWatching_fn;
7768
+ var ProductRecoCarouselLit = class extends LitElement13 {
7859
7769
  constructor() {
7860
7770
  super();
7861
7771
  __privateAdd(this, _ProductRecoCarouselLit_instances);
7772
+ __privateAdd(this, _unwatch3, []);
7862
7773
  this.props = void 0;
7863
7774
  this._parsed = void 0;
7775
+ this._live = {};
7864
7776
  new RenderHealthController(this, () => __privateMethod(this, _ProductRecoCarouselLit_instances, healthProbe_fn3).call(this));
7865
7777
  }
7866
7778
  createRenderRoot() {
@@ -7870,24 +7782,34 @@ var ProductRecoCarouselLit = class extends LitElement12 {
7870
7782
  if (changed.has("props")) {
7871
7783
  const r = this.props ? recoSchema.safeParse(this.props) : void 0;
7872
7784
  this._parsed = r?.success ? r.data : void 0;
7785
+ this._live = {};
7786
+ if (this.isConnected) __privateMethod(this, _ProductRecoCarouselLit_instances, watchPrices_fn).call(this);
7873
7787
  }
7874
7788
  }
7789
+ connectedCallback() {
7790
+ super.connectedCallback();
7791
+ __privateMethod(this, _ProductRecoCarouselLit_instances, watchPrices_fn).call(this);
7792
+ }
7793
+ disconnectedCallback() {
7794
+ super.disconnectedCallback();
7795
+ __privateMethod(this, _ProductRecoCarouselLit_instances, stopWatching_fn).call(this);
7796
+ }
7875
7797
  render() {
7876
7798
  const items = this._parsed?.items ?? [];
7877
- if (items.length === 0) return html13``;
7878
- return html13`
7799
+ if (items.length === 0) return html14``;
7800
+ return html14`
7879
7801
  <section class="sc-reco" style="box-sizing:border-box;display:flex;flex-direction:column;height:100%;padding:2px var(--sc-reco-inset,12px) var(--sc-reco-inset,12px);">
7880
7802
  <div class="sc-reco__carousel" role="list" style="display:flex;align-items:stretch;gap:8px;flex:1 1 auto;min-height:0;overflow-x:auto;scroll-snap-type:x mandatory;">
7881
7803
  ${items.map(
7882
- (it) => html13`
7804
+ (it) => html14`
7883
7805
  <div role="listitem" style="display:contents;">
7884
7806
  <a data-reco-item data-product-cta="primary" href=${it.pdp_href}
7885
7807
  style=${styleMap2({ position: "relative", flex: "0 0 auto", aspectRatio: "4 / 5", minHeight: "var(--sc-reco-item-min-h, 162px)", maxHeight: "var(--sc-reco-item-max-h, 340px)", borderRadius: "12px", overflow: "hidden", scrollSnapAlign: "start", textDecoration: "none", display: "block", background: "#e5e7eb" })}>
7886
- ${it.image_url ? html13`<img src=${it.image_url} alt=${it.hook ?? "Recommended product"} loading="lazy" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;">` : nothing8}
7808
+ ${it.image_url ? html14`<img src=${it.image_url} alt=${it.hook ?? "Recommended product"} loading="lazy" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;">` : nothing9}
7887
7809
  <span style="position:absolute;inset:auto 0 0 0;height:60%;background:linear-gradient(transparent,rgba(0,0,0,.62));"></span>
7888
7810
  <span style="position:absolute;left:8px;right:8px;bottom:8px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.5);">
7889
- ${it.price ? html13`<span style="display:block;font-weight:700;font-size:0.9rem;">${it.price}</span>` : nothing8}
7890
- ${it.hook ? html13`<span style="display:block;font-size:0.72rem;line-height:1.2;opacity:.95;">${it.hook}</span>` : nothing8}
7811
+ ${it.product_id && this._live[it.product_id] || it.price ? html14`<span style="display:block;font-weight:700;font-size:0.9rem;">${it.product_id && this._live[it.product_id] || it.price}</span>` : nothing9}
7812
+ ${it.hook ? html14`<span style="display:block;font-size:0.72rem;line-height:1.2;opacity:.95;">${it.hook}</span>` : nothing9}
7891
7813
  </span>
7892
7814
  </a>
7893
7815
  </div>`
@@ -7896,6 +7818,7 @@ var ProductRecoCarouselLit = class extends LitElement12 {
7896
7818
  </section>`;
7897
7819
  }
7898
7820
  };
7821
+ _unwatch3 = new WeakMap();
7899
7822
  _ProductRecoCarouselLit_instances = new WeakSet();
7900
7823
  /** Measure only the SETTLED carousel (items rendered). An empty carousel
7901
7824
  * renders nothing and is never "blank". NON-PII stateKey. */
@@ -7911,7 +7834,30 @@ healthProbe_fn3 = function() {
7911
7834
  widgetKind: "adaptive-product:reco"
7912
7835
  };
7913
7836
  };
7914
- ProductRecoCarouselLit.properties = { props: { attribute: false }, _parsed: { state: true } };
7837
+ /** The store's live price for each item, now and whenever the runtime
7838
+ * publishes a commerce adapter; an item the store cannot price keeps the
7839
+ * catalog price the server sent. */
7840
+ watchPrices_fn = function() {
7841
+ __privateMethod(this, _ProductRecoCarouselLit_instances, stopWatching_fn).call(this);
7842
+ for (const item of this._parsed?.items ?? []) {
7843
+ const id = item.product_id;
7844
+ if (!id || !item.price) continue;
7845
+ __privateGet(this, _unwatch3).push(
7846
+ watchLivePrice(id, (live) => {
7847
+ this._live = { ...this._live, [id]: live.price };
7848
+ })
7849
+ );
7850
+ }
7851
+ };
7852
+ stopWatching_fn = function() {
7853
+ for (const stop of __privateGet(this, _unwatch3)) stop();
7854
+ __privateSet(this, _unwatch3, []);
7855
+ };
7856
+ ProductRecoCarouselLit.properties = {
7857
+ props: { attribute: false },
7858
+ _parsed: { state: true },
7859
+ _live: { state: true }
7860
+ };
7915
7861
  ProductRecoCarouselLit._testing = {
7916
7862
  healthProbe: (host) => {
7917
7863
  var _a;
@@ -7922,7 +7868,7 @@ if (!customElements.get("syntro-product-reco-carousel"))
7922
7868
  customElements.define("syntro-product-reco-carousel", ProductRecoCarouselLit);
7923
7869
 
7924
7870
  // src/widgets/PdpTrendingNewsLit.ts
7925
- import { html as html14, LitElement as LitElement13 } from "lit";
7871
+ import { html as html15, LitElement as LitElement14 } from "lit";
7926
7872
 
7927
7873
  // src/widgets/entrance.ts
7928
7874
  function armEntranceSettle(onSettle, container, settleMs, fastPath = false) {
@@ -7950,14 +7896,6 @@ function armEntranceSettle(onSettle, container, settleMs, fastPath = false) {
7950
7896
  }
7951
7897
 
7952
7898
  // src/widgets/PdpTrendingNewsLit.ts
7953
- function ensureTrendingNewsStyles() {
7954
- if (typeof document === "undefined") return;
7955
- if (document.getElementById("syntro-pdp-trending-news-style")) return;
7956
- const style = document.createElement("style");
7957
- style.id = "syntro-pdp-trending-news-style";
7958
- style.textContent = TRENDING_NEWS_CSS;
7959
- document.head.appendChild(style);
7960
- }
7961
7899
  var TRENDING_NEWS_CSS = `
7962
7900
  .pdp-trending-news {
7963
7901
  --tn-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8163,11 +8101,12 @@ var TRENDING_NEWS_CSS = `
8163
8101
  outline: none;
8164
8102
  }
8165
8103
  .pdp-tn-headline-link::after {
8166
- content: ' \\2197';
8104
+ /* Decoration: empty alternative text keeps it out of the link's name. */
8105
+ content: ' \\2197' / '';
8167
8106
  font-family: var(--tn-sans);
8168
8107
  font-size: 0.65em;
8169
8108
  letter-spacing: 0;
8170
- color: var(--tn-primary);
8109
+ color: var(--syntro-pdp-primary-ink, var(--tn-primary));
8171
8110
  opacity: 0.55;
8172
8111
  vertical-align: 0.18em;
8173
8112
  margin-left: 0.2em;
@@ -8186,7 +8125,7 @@ var TRENDING_NEWS_CSS = `
8186
8125
  font-style: italic;
8187
8126
  font-size: 11px;
8188
8127
  letter-spacing: 0.06em;
8189
- color: color-mix(in srgb, var(--tn-primary) 70%, transparent);
8128
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--tn-primary) 70%, transparent));
8190
8129
  padding: 3px 9px 3px 8px;
8191
8130
  border-radius: 999px;
8192
8131
  background: color-mix(in srgb, var(--tn-primary) 12%, transparent);
@@ -8269,7 +8208,7 @@ var TRENDING_NEWS_CSS = `
8269
8208
  }
8270
8209
  }
8271
8210
  `;
8272
- var PdpTrendingNewsLit = class extends LitElement13 {
8211
+ var PdpTrendingNewsLit = class extends LitElement14 {
8273
8212
  constructor() {
8274
8213
  super(...arguments);
8275
8214
  this.data = void 0;
@@ -8282,7 +8221,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8282
8221
  }
8283
8222
  connectedCallback() {
8284
8223
  super.connectedCallback();
8285
- ensureTrendingNewsStyles();
8224
+ ensureStylesInRoot(this, "syntro-pdp-trending-news-style", TRENDING_NEWS_CSS);
8286
8225
  if (this._entering) this.requestUpdate();
8287
8226
  }
8288
8227
  disconnectedCallback() {
@@ -8305,7 +8244,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8305
8244
  }
8306
8245
  render() {
8307
8246
  if (!this.data) {
8308
- return html14`<div data-skeleton class="pdp-trending-news-skeleton">
8247
+ return html15`<div data-skeleton class="pdp-trending-news-skeleton">
8309
8248
  <div class="pdp-skeleton-bar" style="width:60%"></div>
8310
8249
  <div class="pdp-skeleton-bar" style="width:90%"></div>
8311
8250
  <div class="pdp-skeleton-bar" style="width:80%"></div>
@@ -8315,7 +8254,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8315
8254
  const items = this.data.items;
8316
8255
  const featured = items[0];
8317
8256
  const rest = items.slice(1);
8318
- return html14`
8257
+ return html15`
8319
8258
  <section
8320
8259
  class="${this._entering ? "pdp-trending-news is-entering" : "pdp-trending-news"}"
8321
8260
  aria-label="Trending news"
@@ -8332,7 +8271,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8332
8271
  <p class="pdp-tn-blurb">${featured.blurb}</p>
8333
8272
  </article>
8334
8273
  ${rest.map(
8335
- (item) => html14`
8274
+ (item) => html15`
8336
8275
  <article class="pdp-tn-card">
8337
8276
  <div class="pdp-tn-meta">
8338
8277
  <span class="pdp-tn-dot" aria-hidden="true"></span>
@@ -8354,8 +8293,8 @@ PdpTrendingNewsLit.properties = {
8354
8293
  _entering: { state: true }
8355
8294
  };
8356
8295
  function renderHeadlineLink(item) {
8357
- if (!item.url) return html14`${item.headline}`;
8358
- return html14`<a
8296
+ if (!item.url) return html15`${item.headline}`;
8297
+ return html15`<a
8359
8298
  class="pdp-tn-headline-link"
8360
8299
  href=${item.url}
8361
8300
  target="_blank"
@@ -8368,15 +8307,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-tre
8368
8307
  }
8369
8308
 
8370
8309
  // src/widgets/PdpTrendingNewsMagazineLit.ts
8371
- import { html as html15, LitElement as LitElement14 } from "lit";
8372
- function ensureMagazineStyles() {
8373
- if (typeof document === "undefined") return;
8374
- if (document.getElementById("syntro-pdp-trending-news-magazine-style")) return;
8375
- const style = document.createElement("style");
8376
- style.id = "syntro-pdp-trending-news-magazine-style";
8377
- style.textContent = MAGAZINE_CSS;
8378
- document.head.appendChild(style);
8379
- }
8310
+ import { html as html16, LitElement as LitElement15 } from "lit";
8380
8311
  var MAGAZINE_CSS = `
8381
8312
  .pdp-tn-mag {
8382
8313
  --tn-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8548,7 +8479,7 @@ var MAGAZINE_CSS = `
8548
8479
  }
8549
8480
  }
8550
8481
  `;
8551
- var PdpTrendingNewsMagazineLit = class extends LitElement14 {
8482
+ var PdpTrendingNewsMagazineLit = class extends LitElement15 {
8552
8483
  constructor() {
8553
8484
  super(...arguments);
8554
8485
  this.data = void 0;
@@ -8558,17 +8489,17 @@ var PdpTrendingNewsMagazineLit = class extends LitElement14 {
8558
8489
  }
8559
8490
  connectedCallback() {
8560
8491
  super.connectedCallback();
8561
- ensureMagazineStyles();
8492
+ ensureStylesInRoot(this, "syntro-pdp-trending-news-magazine-style", MAGAZINE_CSS);
8562
8493
  }
8563
8494
  render() {
8564
8495
  if (!this.data) {
8565
- return html15`<div aria-busy="true" class="pdp-tn-mag" style="opacity:0.5;">…</div>`;
8496
+ return html16`<div aria-busy="true" class="pdp-tn-mag" style="opacity:0.5;">…</div>`;
8566
8497
  }
8567
8498
  const items = this.data.items;
8568
8499
  const [lead, ...rest] = items;
8569
- return html15`
8500
+ return html16`
8570
8501
  <section class="pdp-tn-mag" aria-label="Trending news">
8571
- ${lead ? html15`<article class="pdp-tn-mag__lead">
8502
+ ${lead ? html16`<article class="pdp-tn-mag__lead">
8572
8503
  <div class="pdp-tn-mag__meta">
8573
8504
  <span class="pdp-tn-mag__meta-source">${lead.source}</span>
8574
8505
  · ${lead.published_relative}
@@ -8577,7 +8508,7 @@ var PdpTrendingNewsMagazineLit = class extends LitElement14 {
8577
8508
  <p class="pdp-tn-mag__blurb">${lead.blurb}</p>
8578
8509
  </article>` : ""}
8579
8510
  ${rest.map(
8580
- (it) => html15`<article class="pdp-tn-mag__item">
8511
+ (it) => html16`<article class="pdp-tn-mag__item">
8581
8512
  <div class="pdp-tn-mag__meta">
8582
8513
  <span class="pdp-tn-mag__meta-source">${it.source}</span>
8583
8514
  · ${it.published_relative}
@@ -8594,8 +8525,8 @@ PdpTrendingNewsMagazineLit.properties = {
8594
8525
  data: { attribute: false }
8595
8526
  };
8596
8527
  function renderHeadlineLink2(item) {
8597
- if (!item.url) return html15`${item.headline}`;
8598
- return html15`<a
8528
+ if (!item.url) return html16`${item.headline}`;
8529
+ return html16`<a
8599
8530
  class="pdp-tn-mag__headline-link"
8600
8531
  href=${item.url}
8601
8532
  target="_blank"
@@ -8608,15 +8539,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-tre
8608
8539
  }
8609
8540
 
8610
8541
  // src/widgets/PdpTrendingNewsHeroLit.ts
8611
- import { html as html16, LitElement as LitElement15 } from "lit";
8612
- function ensureHeroStyles() {
8613
- if (typeof document === "undefined") return;
8614
- if (document.getElementById("syntro-pdp-trending-news-hero-style")) return;
8615
- const style = document.createElement("style");
8616
- style.id = "syntro-pdp-trending-news-hero-style";
8617
- style.textContent = HERO_CSS;
8618
- document.head.appendChild(style);
8619
- }
8542
+ import { html as html17, LitElement as LitElement16 } from "lit";
8620
8543
  var HERO_CSS = `
8621
8544
  .pdp-tn-hero {
8622
8545
  --tn-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8799,7 +8722,7 @@ var HERO_CSS = `
8799
8722
  }
8800
8723
  }
8801
8724
  `;
8802
- var PdpTrendingNewsHeroLit = class extends LitElement15 {
8725
+ var PdpTrendingNewsHeroLit = class extends LitElement16 {
8803
8726
  constructor() {
8804
8727
  super(...arguments);
8805
8728
  this.data = void 0;
@@ -8809,15 +8732,15 @@ var PdpTrendingNewsHeroLit = class extends LitElement15 {
8809
8732
  }
8810
8733
  connectedCallback() {
8811
8734
  super.connectedCallback();
8812
- ensureHeroStyles();
8735
+ ensureStylesInRoot(this, "syntro-pdp-trending-news-hero-style", HERO_CSS);
8813
8736
  }
8814
8737
  render() {
8815
8738
  if (!this.data) {
8816
- return html16`<div aria-busy="true" class="pdp-tn-hero" style="opacity:0.5;">…</div>`;
8739
+ return html17`<div aria-busy="true" class="pdp-tn-hero" style="opacity:0.5;">…</div>`;
8817
8740
  }
8818
8741
  const [lead, ...rest] = this.data.items;
8819
- if (!lead) return html16``;
8820
- return html16`
8742
+ if (!lead) return html17``;
8743
+ return html17`
8821
8744
  <section class="pdp-tn-hero" aria-label="Trending news">
8822
8745
  <article class="pdp-tn-hero__panel">
8823
8746
  <blockquote class="pdp-tn-hero__quote">${lead.blurb}</blockquote>
@@ -8828,9 +8751,9 @@ var PdpTrendingNewsHeroLit = class extends LitElement15 {
8828
8751
  </footer>
8829
8752
  <p class="pdp-tn-hero__headline">${renderHeadlineLink3(lead)}</p>
8830
8753
  </article>
8831
- ${rest.length ? html16`<div class="pdp-tn-hero__followon">
8754
+ ${rest.length ? html17`<div class="pdp-tn-hero__followon">
8832
8755
  ${rest.map(
8833
- (it) => html16`<div class="pdp-tn-hero__row">
8756
+ (it) => html17`<div class="pdp-tn-hero__row">
8834
8757
  <p class="pdp-tn-hero__row-headline">${renderHeadlineLink3(it)}</p>
8835
8758
  <span class="pdp-tn-hero__row-meta">${it.source}</span>
8836
8759
  </div>`
@@ -8844,8 +8767,8 @@ PdpTrendingNewsHeroLit.properties = {
8844
8767
  data: { attribute: false }
8845
8768
  };
8846
8769
  function renderHeadlineLink3(item) {
8847
- if (!item.url) return html16`${item.headline}`;
8848
- return html16`<a
8770
+ if (!item.url) return html17`${item.headline}`;
8771
+ return html17`<a
8849
8772
  class="pdp-tn-hero__headline-link"
8850
8773
  href=${item.url}
8851
8774
  target="_blank"
@@ -8858,15 +8781,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-tre
8858
8781
  }
8859
8782
 
8860
8783
  // src/widgets/PdpPeerFeedMasonryLit.ts
8861
- import { html as html17, LitElement as LitElement16 } from "lit";
8862
- function ensureMasonryStyles() {
8863
- if (typeof document === "undefined") return;
8864
- if (document.getElementById("syntro-pdp-peer-feed-masonry-style")) return;
8865
- const style = document.createElement("style");
8866
- style.id = "syntro-pdp-peer-feed-masonry-style";
8867
- style.textContent = MASONRY_CSS;
8868
- document.head.appendChild(style);
8869
- }
8784
+ import { html as html18, LitElement as LitElement17 } from "lit";
8870
8785
  var MASONRY_CSS = `
8871
8786
  .pdp-pf-mas {
8872
8787
  --pf-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8997,12 +8912,12 @@ var MASONRY_CSS = `
8997
8912
  padding-top: 10px;
8998
8913
  }
8999
8914
  `;
9000
- function personaHue(s4) {
8915
+ function personaHue(s) {
9001
8916
  let h = 0;
9002
- for (let i2 = 0; i2 < s4.length; i2++) h = h * 31 + s4.charCodeAt(i2) | 0;
8917
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
9003
8918
  return Math.abs(h) % 360;
9004
8919
  }
9005
- var PdpPeerFeedMasonryLit = class extends LitElement16 {
8920
+ var PdpPeerFeedMasonryLit = class extends LitElement17 {
9006
8921
  constructor() {
9007
8922
  super(...arguments);
9008
8923
  this.data = void 0;
@@ -9012,17 +8927,17 @@ var PdpPeerFeedMasonryLit = class extends LitElement16 {
9012
8927
  }
9013
8928
  connectedCallback() {
9014
8929
  super.connectedCallback();
9015
- ensureMasonryStyles();
8930
+ ensureStylesInRoot(this, "syntro-pdp-peer-feed-masonry-style", MASONRY_CSS);
9016
8931
  }
9017
8932
  render() {
9018
8933
  if (!this.data) {
9019
- return html17`<div aria-busy="true" class="pdp-pf-mas" style="opacity:0.5;">…</div>`;
8934
+ return html18`<div aria-busy="true" class="pdp-pf-mas" style="opacity:0.5;">…</div>`;
9020
8935
  }
9021
- return html17`
8936
+ return html18`
9022
8937
  <section class="pdp-pf-mas" aria-label="Peer voices">
9023
8938
  <div class="pdp-pf-mas__grid">
9024
8939
  ${this.data.peers.map(
9025
- (p) => html17`<article
8940
+ (p) => html18`<article
9026
8941
  class="pdp-pf-mas__card"
9027
8942
  style=${`--pf-avatar-hue: ${personaHue(p.persona)};`}
9028
8943
  >
@@ -9050,15 +8965,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-pee
9050
8965
  }
9051
8966
 
9052
8967
  // src/widgets/PdpPeerFeedCarouselLit.ts
9053
- import { html as html18, LitElement as LitElement17, svg } from "lit";
9054
- function ensureCarouselStyles2() {
9055
- if (typeof document === "undefined") return;
9056
- if (document.getElementById("syntro-pdp-peer-feed-carousel-style")) return;
9057
- const style = document.createElement("style");
9058
- style.id = "syntro-pdp-peer-feed-carousel-style";
9059
- style.textContent = CAROUSEL_CSS2;
9060
- document.head.appendChild(style);
9061
- }
8968
+ import { html as html19, LitElement as LitElement18, svg } from "lit";
9062
8969
  var CAROUSEL_CSS2 = `
9063
8970
  /* The custom-element tag itself + its slot-mount parent must declare
9064
8971
  display:block AND min-width:0 so they participate in the grid cell's
@@ -9227,12 +9134,12 @@ syntro-pdp-peer-feed-carousel {
9227
9134
  var GLYPHS = ["waveform", "gauge", "leaf", "spark"];
9228
9135
  function glyphFor(persona) {
9229
9136
  let h = 0;
9230
- for (let i2 = 0; i2 < persona.length; i2++) h = h * 31 + persona.charCodeAt(i2) | 0;
9137
+ for (let i = 0; i < persona.length; i++) h = h * 31 + persona.charCodeAt(i) | 0;
9231
9138
  return GLYPHS[Math.abs(h) % GLYPHS.length];
9232
9139
  }
9233
9140
  function glyphHue(persona) {
9234
9141
  let h = 0;
9235
- for (let i2 = 0; i2 < persona.length; i2++) h = h * 17 + persona.charCodeAt(i2) | 0;
9142
+ for (let i = 0; i < persona.length; i++) h = h * 17 + persona.charCodeAt(i) | 0;
9236
9143
  return Math.abs(h) % 360;
9237
9144
  }
9238
9145
  function renderGlyph(kind) {
@@ -9266,7 +9173,7 @@ function renderGlyph(kind) {
9266
9173
  <path d="M30 6 L34 26 L54 30 L34 34 L30 54 L26 34 L6 30 L26 26 Z" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round"/>
9267
9174
  </svg>`;
9268
9175
  }
9269
- var PdpPeerFeedCarouselLit = class extends LitElement17 {
9176
+ var PdpPeerFeedCarouselLit = class extends LitElement18 {
9270
9177
  constructor() {
9271
9178
  super(...arguments);
9272
9179
  this.data = void 0;
@@ -9276,19 +9183,19 @@ var PdpPeerFeedCarouselLit = class extends LitElement17 {
9276
9183
  }
9277
9184
  connectedCallback() {
9278
9185
  super.connectedCallback();
9279
- ensureCarouselStyles2();
9186
+ ensureStylesInRoot(this, "syntro-pdp-peer-feed-carousel-style", CAROUSEL_CSS2);
9280
9187
  }
9281
9188
  render() {
9282
9189
  if (!this.data) {
9283
- return html18`<div aria-busy="true" class="pdp-pf-car" style="opacity:0.5;">…</div>`;
9190
+ return html19`<div aria-busy="true" class="pdp-pf-car" style="opacity:0.5;">…</div>`;
9284
9191
  }
9285
- return html18`
9192
+ return html19`
9286
9193
  <section class="pdp-pf-car" aria-label="Peer voices">
9287
9194
  <div class="pdp-pf-car__track">
9288
9195
  ${this.data.peers.map((p) => {
9289
9196
  const kind = glyphFor(p.persona);
9290
9197
  const hue = glyphHue(p.persona);
9291
- return html18`<article class="pdp-pf-car__card" style=${`--pf-glyph-hue: ${hue};`}>
9198
+ return html19`<article class="pdp-pf-car__card" style=${`--pf-glyph-hue: ${hue};`}>
9292
9199
  <span class="pdp-pf-car__glyph" aria-hidden="true">${renderGlyph(kind)}</span>
9293
9200
  <span class="pdp-pf-car__persona">${p.persona}</span>
9294
9201
  <blockquote class="pdp-pf-car__quote">${p.quote}</blockquote>
@@ -9313,7 +9220,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-pee
9313
9220
  }
9314
9221
 
9315
9222
  // src/widgets/PdpChartLit.ts
9316
- import { html as html19, LitElement as LitElement18, nothing as nothing9, svg as svg2 } from "lit";
9223
+ import { html as html20, LitElement as LitElement19, nothing as nothing10, svg as svg2 } from "lit";
9317
9224
  function formatNum(v) {
9318
9225
  if (v === void 0 || !Number.isFinite(v)) return "\u2014";
9319
9226
  if (Number.isInteger(v)) return v.toLocaleString("en-US");
@@ -9323,7 +9230,7 @@ function formatNum(v) {
9323
9230
  return decPart ? `${formatted}.${decPart}` : formatted;
9324
9231
  }
9325
9232
  function buildComparisonMatrix(data) {
9326
- const colLabels = data.series.map((s4) => s4.name);
9233
+ const colLabels = data.series.map((s) => s.name);
9327
9234
  const seenRows = /* @__PURE__ */ new Map();
9328
9235
  const rowLabels = [];
9329
9236
  for (const series of data.series) {
@@ -9391,7 +9298,7 @@ function niceTicks(yMax) {
9391
9298
  const rawStep = yMax / targetCount;
9392
9299
  const mag = 10 ** Math.floor(Math.log10(rawStep));
9393
9300
  const niceSteps = [1, 2, 2.5, 5, 10];
9394
- const step = niceSteps.find((s4) => s4 * mag >= rawStep);
9301
+ const step = niceSteps.find((s) => s * mag >= rawStep);
9395
9302
  const niceStep = step * mag;
9396
9303
  const ticks = [];
9397
9304
  for (let v = 0; v <= yMax + niceStep * 1e-3; v += niceStep) {
@@ -9402,8 +9309,8 @@ function niceTicks(yMax) {
9402
9309
  function buildLinePaths(data) {
9403
9310
  const seenX = /* @__PURE__ */ new Map();
9404
9311
  const xLabels = [];
9405
- for (const s4 of data.series) {
9406
- for (const p of s4.points) {
9312
+ for (const s of data.series) {
9313
+ for (const p of s.points) {
9407
9314
  if (!seenX.has(p.x)) {
9408
9315
  seenX.set(p.x, xLabels.length);
9409
9316
  xLabels.push(p.x);
@@ -9412,16 +9319,16 @@ function buildLinePaths(data) {
9412
9319
  }
9413
9320
  const denom = Math.max(1, xLabels.length - 1);
9414
9321
  let yMax = 0;
9415
- for (const s4 of data.series) {
9416
- for (const p of s4.points) {
9322
+ for (const s of data.series) {
9323
+ for (const p of s.points) {
9417
9324
  if (p.y > yMax) yMax = p.y;
9418
9325
  }
9419
9326
  }
9420
9327
  if (yMax === 0) yMax = 1;
9421
- const seriesOut = data.series.map((s4, sIdx) => ({
9422
- name: s4.name,
9328
+ const seriesOut = data.series.map((s, sIdx) => ({
9329
+ name: s.name,
9423
9330
  seriesIdx: sIdx,
9424
- points: s4.points.map((p) => {
9331
+ points: s.points.map((p) => {
9425
9332
  const xIdx = seenX.get(p.x);
9426
9333
  return {
9427
9334
  cx: xIdx / denom * 100,
@@ -9433,14 +9340,6 @@ function buildLinePaths(data) {
9433
9340
  }));
9434
9341
  return { series: seriesOut, xLabels, yMax };
9435
9342
  }
9436
- function ensureChartStyles() {
9437
- if (typeof document === "undefined") return;
9438
- if (document.getElementById("syntro-pdp-chart-style")) return;
9439
- const style = document.createElement("style");
9440
- style.id = "syntro-pdp-chart-style";
9441
- style.textContent = CHART_CSS;
9442
- document.head.appendChild(style);
9443
- }
9444
9343
  var CHART_CSS = `
9445
9344
  .pdp-chart-frame {
9446
9345
  --pc-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -9808,12 +9707,12 @@ var CHART_CSS = `
9808
9707
  background: linear-gradient(90deg, color-mix(in srgb, var(--pc-primary) 12%, transparent), transparent 80%);
9809
9708
  }
9810
9709
  .pdp-chart-table tbody tr.is-winning td:first-child {
9811
- color: color-mix(in srgb, var(--pc-primary) 70%, white 30%);
9710
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pc-primary) 70%, white 30%));
9812
9711
  }
9813
9712
  .pdp-chart-table tbody tr.is-winning td:first-child::before {
9814
9713
  content: '\\2726 ';
9815
9714
  font-size: 11px;
9816
- color: color-mix(in srgb, var(--pc-primary) 60%, white 40%);
9715
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pc-primary) 60%, white 40%));
9817
9716
  vertical-align: 1px;
9818
9717
  margin-right: 2px;
9819
9718
  }
@@ -9936,7 +9835,7 @@ function colorVar(seriesIdx) {
9936
9835
  return `var(--pc-color-${seriesIdx % PALETTE_SIZE})`;
9937
9836
  }
9938
9837
  var _PdpChartLit_instances, renderLegend_fn, renderTable_fn, renderBars_fn, renderLine_fn;
9939
- var PdpChartLit = class extends LitElement18 {
9838
+ var PdpChartLit = class extends LitElement19 {
9940
9839
  constructor() {
9941
9840
  super(...arguments);
9942
9841
  __privateAdd(this, _PdpChartLit_instances);
@@ -9949,7 +9848,7 @@ var PdpChartLit = class extends LitElement18 {
9949
9848
  }
9950
9849
  connectedCallback() {
9951
9850
  super.connectedCallback();
9952
- ensureChartStyles();
9851
+ ensureStylesInRoot(this, "syntro-pdp-chart-style", CHART_CSS);
9953
9852
  if (this._entering) this.requestUpdate();
9954
9853
  }
9955
9854
  disconnectedCallback() {
@@ -9972,16 +9871,16 @@ var PdpChartLit = class extends LitElement18 {
9972
9871
  }
9973
9872
  render() {
9974
9873
  if (!this.data) {
9975
- return html19`<div data-skeleton class="pdp-chart-skeleton">
9874
+ return html20`<div data-skeleton class="pdp-chart-skeleton">
9976
9875
  <div class="pdp-skeleton-bar" style="width:50%"></div>
9977
9876
  <div class="pdp-skeleton-rect" style="height:200px"></div>
9978
9877
  </div>`;
9979
9878
  }
9980
9879
  const isTable = this.data.viz_kind === "table";
9981
- const allSeriesOnePoint = this.data.series.every((s4) => s4.points.length <= 1);
9880
+ const allSeriesOnePoint = this.data.series.every((s) => s.points.length <= 1);
9982
9881
  const effectiveKind = isTable ? "table" : this.data.viz_kind === "line" && !allSeriesOnePoint ? "line" : "bar";
9983
9882
  const isMultiSeries = this.data.series.length > 1;
9984
- return html19`
9883
+ return html20`
9985
9884
  <section
9986
9885
  class="${this._entering ? "pdp-chart pdp-chart-frame is-entering" : "pdp-chart pdp-chart-frame"}"
9987
9886
  aria-label="${this.data.title}"
@@ -9992,11 +9891,11 @@ var PdpChartLit = class extends LitElement18 {
9992
9891
  </div>
9993
9892
  <h3 class="pdp-chart-title">${this.data.title}</h3>
9994
9893
 
9995
- ${effectiveKind === "table" ? __privateMethod(this, _PdpChartLit_instances, renderTable_fn).call(this) : nothing9}
9996
- ${effectiveKind === "bar" ? __privateMethod(this, _PdpChartLit_instances, renderBars_fn).call(this) : nothing9}
9997
- ${effectiveKind === "line" ? __privateMethod(this, _PdpChartLit_instances, renderLine_fn).call(this) : nothing9}
9998
- ${effectiveKind !== "table" && isMultiSeries ? __privateMethod(this, _PdpChartLit_instances, renderLegend_fn).call(this) : nothing9}
9999
- ${this.data.rationale ? html19`<p class="pdp-chart-rationale">${this.data.rationale}</p>` : nothing9}
9894
+ ${effectiveKind === "table" ? __privateMethod(this, _PdpChartLit_instances, renderTable_fn).call(this) : nothing10}
9895
+ ${effectiveKind === "bar" ? __privateMethod(this, _PdpChartLit_instances, renderBars_fn).call(this) : nothing10}
9896
+ ${effectiveKind === "line" ? __privateMethod(this, _PdpChartLit_instances, renderLine_fn).call(this) : nothing10}
9897
+ ${effectiveKind !== "table" && isMultiSeries ? __privateMethod(this, _PdpChartLit_instances, renderLegend_fn).call(this) : nothing10}
9898
+ ${this.data.rationale ? html20`<p class="pdp-chart-rationale">${this.data.rationale}</p>` : nothing10}
10000
9899
  </section>
10001
9900
  `;
10002
9901
  }
@@ -10004,17 +9903,17 @@ var PdpChartLit = class extends LitElement18 {
10004
9903
  _PdpChartLit_instances = new WeakSet();
10005
9904
  renderLegend_fn = function() {
10006
9905
  const data = this.data;
10007
- return html19`
9906
+ return html20`
10008
9907
  <ol class="pdp-chart-legend" aria-label="Legend">
10009
9908
  ${data.series.map(
10010
- (s4, idx) => html19`
9909
+ (s, idx) => html20`
10011
9910
  <li class="pdp-chart-legend-item">
10012
9911
  <span
10013
9912
  class="pdp-chart-legend-swatch"
10014
9913
  style="background:${colorVar(idx)}"
10015
9914
  aria-hidden="true"
10016
9915
  ></span>
10017
- <span>${s4.name}</span>
9916
+ <span>${s.name}</span>
10018
9917
  </li>
10019
9918
  `
10020
9919
  )}
@@ -10023,22 +9922,22 @@ renderLegend_fn = function() {
10023
9922
  };
10024
9923
  renderTable_fn = function() {
10025
9924
  const matrix = buildComparisonMatrix(this.data);
10026
- return html19`
9925
+ return html20`
10027
9926
  <div class="pdp-chart-table-wrap">
10028
9927
  <table class="pdp-chart-table">
10029
9928
  <thead>
10030
9929
  <tr>
10031
9930
  <th></th>
10032
- ${matrix.colLabels.map((label) => html19`<th>${label}</th>`)}
9931
+ ${matrix.colLabels.map((label) => html20`<th>${label}</th>`)}
10033
9932
  </tr>
10034
9933
  </thead>
10035
9934
  <tbody>
10036
9935
  ${matrix.rowLabels.map(
10037
- (row, rowIdx) => html19`
9936
+ (row, rowIdx) => html20`
10038
9937
  <tr class=${rowIdx === matrix.winningRow ? "is-winning" : ""}>
10039
9938
  <td>${row}</td>
10040
9939
  ${matrix.cells[rowIdx].map(
10041
- (cell) => html19`<td class="pdp-chart-numeric-cell">${formatNum(cell)}</td>`
9940
+ (cell) => html20`<td class="pdp-chart-numeric-cell">${formatNum(cell)}</td>`
10042
9941
  )}
10043
9942
  </tr>
10044
9943
  `
@@ -10053,7 +9952,7 @@ renderBars_fn = function() {
10053
9952
  const { buckets, yMax } = buildBarBuckets(data);
10054
9953
  const ticks = niceTicks(yMax);
10055
9954
  const scaleMax = Math.max(yMax, ticks[ticks.length - 1]);
10056
- return html19`
9955
+ return html20`
10057
9956
  <div
10058
9957
  class="pdp-chart-bars"
10059
9958
  role="figure"
@@ -10062,33 +9961,33 @@ renderBars_fn = function() {
10062
9961
  >
10063
9962
  <div class="pdp-chart-yaxis" aria-hidden="true">
10064
9963
  ${ticks.map(
10065
- (t2) => html19`
9964
+ (t) => html20`
10066
9965
  <span
10067
9966
  class="pdp-chart-yaxis-tick"
10068
- style="bottom:${t2 / scaleMax * 100}%"
9967
+ style="bottom:${t / scaleMax * 100}%"
10069
9968
  >
10070
- ${formatNum(t2)}
9969
+ ${formatNum(t)}
10071
9970
  </span>
10072
9971
  `
10073
9972
  )}
10074
9973
  </div>
10075
9974
  <div class="pdp-chart-bars-area">
10076
9975
  ${ticks.map(
10077
- (t2) => html19`
9976
+ (t) => html20`
10078
9977
  <div
10079
9978
  class="pdp-chart-bars-gridline"
10080
- style="bottom:${t2 / scaleMax * 100}%"
9979
+ style="bottom:${t / scaleMax * 100}%"
10081
9980
  aria-hidden="true"
10082
9981
  ></div>
10083
9982
  `
10084
9983
  )}
10085
9984
  <div class="pdp-chart-bars-baseline" aria-hidden="true"></div>
10086
9985
  ${buckets.map(
10087
- (bucket, bIdx) => html19`
9986
+ (bucket, bIdx) => html20`
10088
9987
  <div class="pdp-chart-bars-bucket">
10089
9988
  <div class="pdp-chart-bars-stack">
10090
9989
  ${bucket.bars.map(
10091
- (bar, barIdxInBucket) => html19`
9990
+ (bar, barIdxInBucket) => html20`
10092
9991
  <div
10093
9992
  class="pdp-chart-bar"
10094
9993
  style="
@@ -10099,9 +9998,9 @@ renderBars_fn = function() {
10099
9998
  role="img"
10100
9999
  aria-label="${bucket.label} — ${bar.seriesName}: ${formatNum(bar.value)}"
10101
10000
  >
10102
- ${bucket.bars.length === 1 ? html19`<span class="pdp-chart-bar-value"
10001
+ ${bucket.bars.length === 1 ? html20`<span class="pdp-chart-bar-value"
10103
10002
  >${formatNum(bar.value)}</span
10104
- >` : nothing9}
10003
+ >` : nothing10}
10105
10004
  </div>
10106
10005
  `
10107
10006
  )}
@@ -10119,7 +10018,7 @@ renderLine_fn = function() {
10119
10018
  const { series, xLabels, yMax } = buildLinePaths(data);
10120
10019
  const ticks = niceTicks(yMax);
10121
10020
  const scaleMax = Math.max(yMax, ticks[ticks.length - 1]);
10122
- return html19`
10021
+ return html20`
10123
10022
  <div
10124
10023
  class="pdp-chart-line"
10125
10024
  role="figure"
@@ -10129,12 +10028,12 @@ renderLine_fn = function() {
10129
10028
  >
10130
10029
  <div class="pdp-chart-line-yaxis" aria-hidden="true">
10131
10030
  ${ticks.map(
10132
- (t2) => html19`
10031
+ (t) => html20`
10133
10032
  <span
10134
10033
  class="pdp-chart-yaxis-tick"
10135
- style="bottom:${t2 / scaleMax * 100}%"
10034
+ style="bottom:${t / scaleMax * 100}%"
10136
10035
  >
10137
- ${formatNum(t2)}
10036
+ ${formatNum(t)}
10138
10037
  </span>
10139
10038
  `
10140
10039
  )}
@@ -10146,25 +10045,25 @@ renderLine_fn = function() {
10146
10045
  preserveAspectRatio="none"
10147
10046
  aria-hidden="true"
10148
10047
  >
10149
- ${ticks.map((t2) => {
10150
- const y = 60 - t2 / scaleMax * 56 - 2;
10048
+ ${ticks.map((t) => {
10049
+ const y = 60 - t / scaleMax * 56 - 2;
10151
10050
  return svg2`<line class="pdp-chart-line-gridline" x1="0" y1=${y} x2="100" y2=${y} />`;
10152
10051
  })}
10153
10052
  ${series.map(
10154
- (s4) => svg2`
10053
+ (s) => svg2`
10155
10054
  <polyline
10156
10055
  class="pdp-chart-line-stroke"
10157
- style=${`--line-color:${colorVar(s4.seriesIdx)}; --delay:${s4.seriesIdx * 200}ms`}
10158
- points=${s4.points.map((p) => `${p.cx},${p.cy}`).join(" ")}
10056
+ style=${`--line-color:${colorVar(s.seriesIdx)}; --delay:${s.seriesIdx * 200}ms`}
10057
+ points=${s.points.map((p) => `${p.cx},${p.cy}`).join(" ")}
10159
10058
  />
10160
- ${s4.points.map(
10059
+ ${s.points.map(
10161
10060
  (p, idx) => svg2`
10162
10061
  <circle
10163
10062
  class="pdp-chart-line-dot"
10164
10063
  cx=${p.cx}
10165
10064
  cy=${p.cy}
10166
10065
  r="1.4"
10167
- style=${`--line-color:${colorVar(s4.seriesIdx)}; --delay:${s4.seriesIdx * 200}ms; --dot-delay:${800 + idx * 60}ms`}
10066
+ style=${`--line-color:${colorVar(s.seriesIdx)}; --delay:${s.seriesIdx * 200}ms; --dot-delay:${800 + idx * 60}ms`}
10168
10067
  />
10169
10068
  `
10170
10069
  )}
@@ -10178,7 +10077,7 @@ renderLine_fn = function() {
10178
10077
  style="--col-count:${xLabels.length}"
10179
10078
  aria-hidden="true"
10180
10079
  >
10181
- ${xLabels.map((label) => html19`<span>${label}</span>`)}
10080
+ ${xLabels.map((label) => html20`<span>${label}</span>`)}
10182
10081
  </div>
10183
10082
  `;
10184
10083
  };
@@ -10191,15 +10090,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-cha
10191
10090
  }
10192
10091
 
10193
10092
  // src/widgets/PdpChartBarStackedLit.ts
10194
- import { html as html20, LitElement as LitElement19 } from "lit";
10195
- function ensureStyles() {
10196
- if (typeof document === "undefined") return;
10197
- if (document.getElementById("syntro-pdp-chart-bar-style")) return;
10198
- const s4 = document.createElement("style");
10199
- s4.id = "syntro-pdp-chart-bar-style";
10200
- s4.textContent = CSS;
10201
- document.head.appendChild(s4);
10202
- }
10093
+ import { html as html21, LitElement as LitElement20 } from "lit";
10203
10094
  var CSS = `
10204
10095
  .pdp-chart-bar {
10205
10096
  --c-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10279,7 +10170,7 @@ var CSS = `
10279
10170
  }
10280
10171
  `;
10281
10172
  var PALETTE = ["hsl(150 32% 50%)", "hsl(40 75% 60%)", "hsl(208 50% 55%)", "hsl(320 38% 60%)"];
10282
- var PdpChartBarStackedLit = class extends LitElement19 {
10173
+ var PdpChartBarStackedLit = class extends LitElement20 {
10283
10174
  constructor() {
10284
10175
  super(...arguments);
10285
10176
  this.data = void 0;
@@ -10289,39 +10180,39 @@ var PdpChartBarStackedLit = class extends LitElement19 {
10289
10180
  }
10290
10181
  connectedCallback() {
10291
10182
  super.connectedCallback();
10292
- ensureStyles();
10183
+ ensureStylesInRoot(this, "syntro-pdp-chart-bar-style", CSS);
10293
10184
  }
10294
10185
  render() {
10295
- if (!this.data) return html20`<div class="pdp-chart-bar" style="opacity:0.5;">…</div>`;
10186
+ if (!this.data) return html21`<div class="pdp-chart-bar" style="opacity:0.5;">…</div>`;
10296
10187
  const { title, series } = this.data;
10297
- const allX = Array.from(new Set(series.flatMap((s4) => s4.points.map((p) => p.x))));
10298
- const max = Math.max(...series.flatMap((s4) => s4.points.map((p) => p.y)), 1);
10299
- return html20`
10188
+ const allX = Array.from(new Set(series.flatMap((s) => s.points.map((p) => p.x))));
10189
+ const max = Math.max(...series.flatMap((s) => s.points.map((p) => p.y)), 1);
10190
+ return html21`
10300
10191
  <section class="pdp-chart-bar" aria-label=${title}>
10301
10192
  <h3 class="pdp-chart-bar__title">${title}</h3>
10302
- ${series.length > 1 ? html20`<div class="pdp-chart-bar__legend">
10193
+ ${series.length > 1 ? html21`<div class="pdp-chart-bar__legend">
10303
10194
  ${series.map(
10304
- (s4, i2) => html20`<span
10195
+ (s, i) => html21`<span
10305
10196
  ><span
10306
10197
  class="pdp-chart-bar__legend-dot"
10307
- style=${`background: ${PALETTE[i2 % PALETTE.length]};`}
10198
+ style=${`background: ${PALETTE[i % PALETTE.length]};`}
10308
10199
  ></span
10309
- >${s4.name}</span
10200
+ >${s.name}</span
10310
10201
  >`
10311
10202
  )}
10312
10203
  </div>` : ""}
10313
10204
  ${allX.map(
10314
- (x) => html20`<div class="pdp-chart-bar__row">
10205
+ (x) => html21`<div class="pdp-chart-bar__row">
10315
10206
  <div class="pdp-chart-bar__row-label">${x}</div>
10316
10207
  <div class="pdp-chart-bar__row-bars">
10317
- ${series.map((s4, i2) => {
10318
- const point = s4.points.find((p) => p.x === x);
10208
+ ${series.map((s, i) => {
10209
+ const point = s.points.find((p) => p.x === x);
10319
10210
  if (!point) return "";
10320
10211
  const pct = point.y / max * 100;
10321
- return html20`<div class="pdp-chart-bar__bar-track">
10212
+ return html21`<div class="pdp-chart-bar__bar-track">
10322
10213
  <div
10323
10214
  class="pdp-chart-bar__bar-fill"
10324
- style=${`width: ${pct.toFixed(1)}%; background: ${PALETTE[i2 % PALETTE.length]};`}
10215
+ style=${`width: ${pct.toFixed(1)}%; background: ${PALETTE[i % PALETTE.length]};`}
10325
10216
  ></div>
10326
10217
  <span class="pdp-chart-bar__bar-value">${point.y}</span>
10327
10218
  </div>`;
@@ -10341,15 +10232,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-cha
10341
10232
  }
10342
10233
 
10343
10234
  // src/widgets/PdpChartMetricPullLit.ts
10344
- import { html as html21, LitElement as LitElement20, svg as svg3 } from "lit";
10345
- function ensureStyles2() {
10346
- if (typeof document === "undefined") return;
10347
- if (document.getElementById("syntro-pdp-chart-metric-style")) return;
10348
- const s4 = document.createElement("style");
10349
- s4.id = "syntro-pdp-chart-metric-style";
10350
- s4.textContent = CSS2;
10351
- document.head.appendChild(s4);
10352
- }
10235
+ import { html as html22, LitElement as LitElement21, svg as svg3 } from "lit";
10353
10236
  var CSS2 = `
10354
10237
  .pdp-chart-met {
10355
10238
  --c-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10443,17 +10326,17 @@ var CSS2 = `
10443
10326
  function buildSparkline(points) {
10444
10327
  const w = 200;
10445
10328
  const h = 38;
10446
- const xs = points.map((_, i2) => i2 / Math.max(points.length - 1, 1) * (w - 4) + 2);
10329
+ const xs = points.map((_, i) => i / Math.max(points.length - 1, 1) * (w - 4) + 2);
10447
10330
  const min = Math.min(...points.map((p) => p.y));
10448
10331
  const max = Math.max(...points.map((p) => p.y));
10449
10332
  const range = max - min || 1;
10450
10333
  const ys = points.map((p) => h - 4 - (p.y - min) / range * (h - 8));
10451
- const line = xs.map((x, i2) => `${i2 === 0 ? "M" : "L"} ${x.toFixed(1)} ${ys[i2].toFixed(1)}`).join(" ");
10334
+ const line = xs.map((x, i) => `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${ys[i].toFixed(1)}`).join(" ");
10452
10335
  const area = `${line} L ${xs[xs.length - 1].toFixed(1)} ${h} L ${xs[0].toFixed(1)} ${h} Z`;
10453
- const peakIndex = points.reduce((best, p, i2) => p.y > points[best].y ? i2 : best, 0);
10336
+ const peakIndex = points.reduce((best, p, i) => p.y > points[best].y ? i : best, 0);
10454
10337
  return { linePath: line, areaPath: area, peakIndex };
10455
10338
  }
10456
- var PdpChartMetricPullLit = class extends LitElement20 {
10339
+ var PdpChartMetricPullLit = class extends LitElement21 {
10457
10340
  constructor() {
10458
10341
  super(...arguments);
10459
10342
  this.data = void 0;
@@ -10463,16 +10346,16 @@ var PdpChartMetricPullLit = class extends LitElement20 {
10463
10346
  }
10464
10347
  connectedCallback() {
10465
10348
  super.connectedCallback();
10466
- ensureStyles2();
10349
+ ensureStylesInRoot(this, "syntro-pdp-chart-metric-style", CSS2);
10467
10350
  }
10468
10351
  render() {
10469
- if (!this.data) return html21`<div class="pdp-chart-met" style="opacity:0.5;">…</div>`;
10352
+ if (!this.data) return html22`<div class="pdp-chart-met" style="opacity:0.5;">…</div>`;
10470
10353
  const { title, series } = this.data;
10471
10354
  const first = series[0];
10472
- if (!first || first.points.length === 0) return html21``;
10355
+ if (!first || first.points.length === 0) return html22``;
10473
10356
  const points = first.points;
10474
10357
  const peak = points.reduce((b, p) => p.y > b.y ? p : b, points[0]);
10475
- const mean = points.reduce((s4, p) => s4 + p.y, 0) / points.length;
10358
+ const mean = points.reduce((s, p) => s + p.y, 0) / points.length;
10476
10359
  const delta = (peak.y - mean) / Math.max(mean, 1) * 100;
10477
10360
  const spark = buildSparkline(points);
10478
10361
  const w = 200;
@@ -10482,7 +10365,7 @@ var PdpChartMetricPullLit = class extends LitElement20 {
10482
10365
  const max = Math.max(...points.map((p) => p.y));
10483
10366
  const range = max - min || 1;
10484
10367
  const py = h - 4 - (peak.y - min) / range * (h - 8);
10485
- return html21`
10368
+ return html22`
10486
10369
  <section class="pdp-chart-met" aria-label=${title}>
10487
10370
  <div class="pdp-chart-met__metric">
10488
10371
  <span class="pdp-chart-met__big">${peak.y}</span>
@@ -10514,15 +10397,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-cha
10514
10397
  }
10515
10398
 
10516
10399
  // src/widgets/PdpQaAccordionLit.ts
10517
- import { html as html22, LitElement as LitElement21 } from "lit";
10518
- function ensureStyles3() {
10519
- if (typeof document === "undefined") return;
10520
- if (document.getElementById("syntro-pdp-qa-accordion-style")) return;
10521
- const s4 = document.createElement("style");
10522
- s4.id = "syntro-pdp-qa-accordion-style";
10523
- s4.textContent = CSS3;
10524
- document.head.appendChild(s4);
10525
- }
10400
+ import { html as html23, LitElement as LitElement22 } from "lit";
10526
10401
  var CSS3 = `
10527
10402
  .pdp-qa-acc {
10528
10403
  --qa-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10596,7 +10471,7 @@ var CSS3 = `
10596
10471
  max-width: 56ch;
10597
10472
  }
10598
10473
  `;
10599
- var PdpQaAccordionLit = class extends LitElement21 {
10474
+ var PdpQaAccordionLit = class extends LitElement22 {
10600
10475
  constructor() {
10601
10476
  super(...arguments);
10602
10477
  this.data = void 0;
@@ -10606,14 +10481,14 @@ var PdpQaAccordionLit = class extends LitElement21 {
10606
10481
  }
10607
10482
  connectedCallback() {
10608
10483
  super.connectedCallback();
10609
- ensureStyles3();
10484
+ ensureStylesInRoot(this, "syntro-pdp-qa-accordion-style", CSS3);
10610
10485
  }
10611
10486
  render() {
10612
- if (!this.data) return html22`<div class="pdp-qa-acc" style="opacity:0.5;">…</div>`;
10613
- return html22`
10487
+ if (!this.data) return html23`<div class="pdp-qa-acc" style="opacity:0.5;">…</div>`;
10488
+ return html23`
10614
10489
  <section class="pdp-qa-acc" aria-label="Frequently asked questions">
10615
10490
  ${this.data.items.map(
10616
- (it) => html22`<details class="pdp-qa-acc__item">
10491
+ (it) => html23`<details class="pdp-qa-acc__item">
10617
10492
  <summary>
10618
10493
  <span class="pdp-qa-acc__chev" aria-hidden="true"></span>
10619
10494
  <span>${it.question}</span>
@@ -10633,15 +10508,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-qa-
10633
10508
  }
10634
10509
 
10635
10510
  // src/widgets/PdpQaSideBySideLit.ts
10636
- import { html as html23, LitElement as LitElement22 } from "lit";
10637
- function ensureStyles4() {
10638
- if (typeof document === "undefined") return;
10639
- if (document.getElementById("syntro-pdp-qa-side-style")) return;
10640
- const s4 = document.createElement("style");
10641
- s4.id = "syntro-pdp-qa-side-style";
10642
- s4.textContent = CSS4;
10643
- document.head.appendChild(s4);
10644
- }
10511
+ import { html as html24, LitElement as LitElement23 } from "lit";
10645
10512
  var CSS4 = `
10646
10513
  .pdp-qa-side {
10647
10514
  --qa-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10707,7 +10574,7 @@ var CSS4 = `
10707
10574
  margin: 0;
10708
10575
  }
10709
10576
  `;
10710
- var PdpQaSideBySideLit = class extends LitElement22 {
10577
+ var PdpQaSideBySideLit = class extends LitElement23 {
10711
10578
  constructor() {
10712
10579
  super(...arguments);
10713
10580
  this.data = void 0;
@@ -10717,14 +10584,14 @@ var PdpQaSideBySideLit = class extends LitElement22 {
10717
10584
  }
10718
10585
  connectedCallback() {
10719
10586
  super.connectedCallback();
10720
- ensureStyles4();
10587
+ ensureStylesInRoot(this, "syntro-pdp-qa-side-style", CSS4);
10721
10588
  }
10722
10589
  render() {
10723
- if (!this.data) return html23`<div class="pdp-qa-side" style="opacity:0.5;">…</div>`;
10724
- return html23`
10590
+ if (!this.data) return html24`<div class="pdp-qa-side" style="opacity:0.5;">…</div>`;
10591
+ return html24`
10725
10592
  <section class="pdp-qa-side" aria-label="Q&A">
10726
10593
  ${this.data.items.map(
10727
- (it) => html23`<div class="pdp-qa-side__item">
10594
+ (it) => html24`<div class="pdp-qa-side__item">
10728
10595
  <h4 class="pdp-qa-side__q">${it.question}</h4>
10729
10596
  <p class="pdp-qa-side__a">${it.answer}</p>
10730
10597
  </div>`
@@ -10741,15 +10608,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-qa-
10741
10608
  }
10742
10609
 
10743
10610
  // src/widgets/PdpRegionalFullBleedLit.ts
10744
- import { html as html24, LitElement as LitElement23 } from "lit";
10745
- function ensureStyles5() {
10746
- if (typeof document === "undefined") return;
10747
- if (document.getElementById("syntro-pdp-regional-fullbleed-style")) return;
10748
- const s4 = document.createElement("style");
10749
- s4.id = "syntro-pdp-regional-fullbleed-style";
10750
- s4.textContent = CSS5;
10751
- document.head.appendChild(s4);
10752
- }
10611
+ import { html as html25, LitElement as LitElement24 } from "lit";
10753
10612
  var CSS5 = `
10754
10613
  .pdp-reg-fb {
10755
10614
  --r-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10832,7 +10691,7 @@ var CSS5 = `
10832
10691
  font-style: italic;
10833
10692
  }
10834
10693
  `;
10835
- var PdpRegionalFullBleedLit = class extends LitElement23 {
10694
+ var PdpRegionalFullBleedLit = class extends LitElement24 {
10836
10695
  constructor() {
10837
10696
  super(...arguments);
10838
10697
  this.data = void 0;
@@ -10842,12 +10701,12 @@ var PdpRegionalFullBleedLit = class extends LitElement23 {
10842
10701
  }
10843
10702
  connectedCallback() {
10844
10703
  super.connectedCallback();
10845
- ensureStyles5();
10704
+ ensureStylesInRoot(this, "syntro-pdp-regional-fullbleed-style", CSS5);
10846
10705
  }
10847
10706
  render() {
10848
- if (!this.data) return html24`<div class="pdp-reg-fb" style="opacity:0.5;">…</div>`;
10707
+ if (!this.data) return html25`<div class="pdp-reg-fb" style="opacity:0.5;">…</div>`;
10849
10708
  const d = this.data;
10850
- return html24`
10709
+ return html25`
10851
10710
  <section class="pdp-reg-fb" aria-label="Where you are">
10852
10711
  <article class="pdp-reg-fb__panel">
10853
10712
  <svg class="pdp-reg-fb__glyph" viewBox="0 0 56 56" aria-hidden="true">
@@ -10890,15 +10749,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-reg
10890
10749
  }
10891
10750
 
10892
10751
  // src/widgets/PdpRegionalInlineNoteLit.ts
10893
- import { html as html25, LitElement as LitElement24 } from "lit";
10894
- function ensureStyles6() {
10895
- if (typeof document === "undefined") return;
10896
- if (document.getElementById("syntro-pdp-regional-inline-style")) return;
10897
- const s4 = document.createElement("style");
10898
- s4.id = "syntro-pdp-regional-inline-style";
10899
- s4.textContent = CSS6;
10900
- document.head.appendChild(s4);
10901
- }
10752
+ import { html as html26, LitElement as LitElement25 } from "lit";
10902
10753
  var CSS6 = `
10903
10754
  .pdp-reg-in {
10904
10755
  --r-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10951,7 +10802,7 @@ var CSS6 = `
10951
10802
  font-style: italic;
10952
10803
  }
10953
10804
  `;
10954
- var PdpRegionalInlineNoteLit = class extends LitElement24 {
10805
+ var PdpRegionalInlineNoteLit = class extends LitElement25 {
10955
10806
  constructor() {
10956
10807
  super(...arguments);
10957
10808
  this.data = void 0;
@@ -10961,12 +10812,12 @@ var PdpRegionalInlineNoteLit = class extends LitElement24 {
10961
10812
  }
10962
10813
  connectedCallback() {
10963
10814
  super.connectedCallback();
10964
- ensureStyles6();
10815
+ ensureStylesInRoot(this, "syntro-pdp-regional-inline-style", CSS6);
10965
10816
  }
10966
10817
  render() {
10967
- if (!this.data) return html25`<div class="pdp-reg-in" style="opacity:0.5;">…</div>`;
10818
+ if (!this.data) return html26`<div class="pdp-reg-in" style="opacity:0.5;">…</div>`;
10968
10819
  const d = this.data;
10969
- return html25`
10820
+ return html26`
10970
10821
  <aside class="pdp-reg-in" aria-label="Regional note">
10971
10822
  <svg class="pdp-reg-in__icon" viewBox="0 0 22 22" aria-hidden="true">
10972
10823
  <circle cx="11" cy="11" r="9" fill="none" stroke="currentColor" stroke-width="1.6"/>
@@ -10994,8 +10845,8 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-reg
10994
10845
  }
10995
10846
 
10996
10847
  // src/widgets/PdpSlotPlaceholderLit.ts
10997
- import { html as html26, LitElement as LitElement25 } from "lit";
10998
- var PdpSlotPlaceholderLit = class extends LitElement25 {
10848
+ import { html as html27, LitElement as LitElement26 } from "lit";
10849
+ var PdpSlotPlaceholderLit = class extends LitElement26 {
10999
10850
  constructor() {
11000
10851
  super(...arguments);
11001
10852
  this.label = "More personalized content coming soon";
@@ -11004,7 +10855,7 @@ var PdpSlotPlaceholderLit = class extends LitElement25 {
11004
10855
  return this;
11005
10856
  }
11006
10857
  render() {
11007
- return html26`
10858
+ return html27`
11008
10859
  <div class="pdp-slot-placeholder" data-placeholder>
11009
10860
  <p>${this.label}</p>
11010
10861
  <small>Ask in chat to explore this product further.</small>
@@ -11020,15 +10871,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-slo
11020
10871
  }
11021
10872
 
11022
10873
  // src/widgets/PdpRibbonLit.ts
11023
- import { html as html27, LitElement as LitElement26, nothing as nothing10 } from "lit";
11024
- function ensureRibbonStyles() {
11025
- if (typeof document === "undefined") return;
11026
- if (document.getElementById("syntro-pdp-ribbon-style")) return;
11027
- const s4 = document.createElement("style");
11028
- s4.id = "syntro-pdp-ribbon-style";
11029
- s4.textContent = RIBBON_CSS;
11030
- document.head.appendChild(s4);
11031
- }
10874
+ import { html as html28, LitElement as LitElement27, nothing as nothing11 } from "lit";
11032
10875
  var RIBBON_CSS = `
11033
10876
  .syntro-pdp-ribbon {
11034
10877
  --rb-primary: var(--syntro-pdp-primary, hsl(150 32% 38%));
@@ -11087,7 +10930,7 @@ var RIBBON_CSS = `
11087
10930
  width: 16px;
11088
10931
  height: 16px;
11089
10932
  display: inline-block;
11090
- color: var(--rb-primary);
10933
+ color: var(--syntro-pdp-primary-ink, var(--rb-primary));
11091
10934
  }
11092
10935
  .syntro-pdp-ribbon-copy {
11093
10936
  font-family: var(--rb-display);
@@ -11104,7 +10947,7 @@ var RIBBON_CSS = `
11104
10947
  font-style: normal;
11105
10948
  font-family: var(--syntro-pdp-sans, inherit);
11106
10949
  font-weight: 500;
11107
- color: var(--rb-primary);
10950
+ color: var(--syntro-pdp-primary-ink, var(--rb-primary));
11108
10951
  font-size: 14.5px;
11109
10952
  letter-spacing: 0;
11110
10953
  }
@@ -11149,7 +10992,7 @@ var RIBBON_CSS = `
11149
10992
  100% { transform: translateX(110%); }
11150
10993
  }
11151
10994
  `;
11152
- var PdpRibbonLit = class extends LitElement26 {
10995
+ var PdpRibbonLit = class extends LitElement27 {
11153
10996
  constructor() {
11154
10997
  super(...arguments);
11155
10998
  this.topic = "";
@@ -11163,7 +11006,7 @@ var PdpRibbonLit = class extends LitElement26 {
11163
11006
  }
11164
11007
  connectedCallback() {
11165
11008
  super.connectedCallback();
11166
- ensureRibbonStyles();
11009
+ ensureStylesInRoot(this, "syntro-pdp-ribbon-style", RIBBON_CSS);
11167
11010
  if (this._entering) this.requestUpdate();
11168
11011
  }
11169
11012
  disconnectedCallback() {
@@ -11187,7 +11030,7 @@ var PdpRibbonLit = class extends LitElement26 {
11187
11030
  }
11188
11031
  render() {
11189
11032
  const cls = this._entering ? "syntro-pdp-ribbon is-entering" : "syntro-pdp-ribbon";
11190
- return html27`
11033
+ return html28`
11191
11034
  <div class="${cls}" role="status" aria-live="polite">
11192
11035
  <span class="syntro-pdp-ribbon-sweep" aria-hidden="true"></span>
11193
11036
  <svg
@@ -11207,7 +11050,7 @@ var PdpRibbonLit = class extends LitElement26 {
11207
11050
  </svg>
11208
11051
  <p class="syntro-pdp-ribbon-copy">
11209
11052
  Tailored to your conversation about
11210
- ${this.topic ? html27` <span class="syntro-pdp-ribbon-topic">${this.topic}</span>.` : nothing10}
11053
+ ${this.topic ? html28` <span class="syntro-pdp-ribbon-topic">${this.topic}</span>.` : nothing11}
11211
11054
  <span class="syntro-pdp-ribbon-trail"> ${this.subtitle}</span>
11212
11055
  </p>
11213
11056
  </div>
@@ -11224,15 +11067,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-rib
11224
11067
  }
11225
11068
 
11226
11069
  // src/widgets/PdpQaLit.ts
11227
- import { html as html28, LitElement as LitElement27, nothing as nothing11 } from "lit";
11228
- function ensureQaStyles() {
11229
- if (typeof document === "undefined") return;
11230
- if (document.getElementById("syntro-pdp-qa-style")) return;
11231
- const s4 = document.createElement("style");
11232
- s4.id = "syntro-pdp-qa-style";
11233
- s4.textContent = QA_CSS;
11234
- document.head.appendChild(s4);
11235
- }
11070
+ import { html as html29, LitElement as LitElement28, nothing as nothing12 } from "lit";
11236
11071
  var QA_CSS = `
11237
11072
  .pdp-qa {
11238
11073
  --qa-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -11314,7 +11149,7 @@ var QA_CSS = `
11314
11149
  background: color-mix(in srgb, var(--qa-primary) 14%, transparent);
11315
11150
  display: grid;
11316
11151
  place-items: center;
11317
- color: color-mix(in srgb, var(--qa-primary) 80%, white 20%);
11152
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--qa-primary) 80%, white 20%));
11318
11153
  transition: background 200ms ease, transform 200ms ease;
11319
11154
  }
11320
11155
  .pdp-qa-item[open] .pdp-qa-toggle {
@@ -11386,7 +11221,7 @@ var QA_CSS = `
11386
11221
  }
11387
11222
  }
11388
11223
  `;
11389
- var PdpQaLit = class extends LitElement27 {
11224
+ var PdpQaLit = class extends LitElement28 {
11390
11225
  constructor() {
11391
11226
  super(...arguments);
11392
11227
  this.data = void 0;
@@ -11398,7 +11233,7 @@ var PdpQaLit = class extends LitElement27 {
11398
11233
  }
11399
11234
  connectedCallback() {
11400
11235
  super.connectedCallback();
11401
- ensureQaStyles();
11236
+ ensureStylesInRoot(this, "syntro-pdp-qa-style", QA_CSS);
11402
11237
  if (this._entering) this.requestUpdate();
11403
11238
  }
11404
11239
  disconnectedCallback() {
@@ -11421,19 +11256,19 @@ var PdpQaLit = class extends LitElement27 {
11421
11256
  }
11422
11257
  render() {
11423
11258
  if (!this.data) {
11424
- return html28`<div data-skeleton class="pdp-qa-skeleton">
11259
+ return html29`<div data-skeleton class="pdp-qa-skeleton">
11425
11260
  <div class="pdp-qa-skeleton-row"></div>
11426
11261
  <div class="pdp-qa-skeleton-row"></div>
11427
11262
  <div class="pdp-qa-skeleton-row"></div>
11428
11263
  </div>`;
11429
11264
  }
11430
- return html28`
11265
+ return html29`
11431
11266
  <section
11432
11267
  class="${this._entering ? "pdp-qa is-entering" : "pdp-qa"}"
11433
11268
  aria-label="Frequently asked questions"
11434
11269
  >
11435
11270
  ${this.data.items.map(
11436
- (item, idx) => html28`
11271
+ (item, idx) => html29`
11437
11272
  <details class="pdp-qa-item" ?open=${idx === 0}>
11438
11273
  <summary class="pdp-qa-question">
11439
11274
  <span>${item.question}</span>
@@ -11448,7 +11283,7 @@ var PdpQaLit = class extends LitElement27 {
11448
11283
  </details>
11449
11284
  `
11450
11285
  )}
11451
- ${nothing11}
11286
+ ${nothing12}
11452
11287
  </section>
11453
11288
  `;
11454
11289
  }
@@ -11462,15 +11297,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-qa"
11462
11297
  }
11463
11298
 
11464
11299
  // src/widgets/PdpRegionalLit.ts
11465
- import { html as html29, LitElement as LitElement28 } from "lit";
11466
- function ensureRegionalStyles() {
11467
- if (typeof document === "undefined") return;
11468
- if (document.getElementById("syntro-pdp-regional-style")) return;
11469
- const s4 = document.createElement("style");
11470
- s4.id = "syntro-pdp-regional-style";
11471
- s4.textContent = REGIONAL_CSS;
11472
- document.head.appendChild(s4);
11473
- }
11300
+ import { html as html30, LitElement as LitElement29 } from "lit";
11474
11301
  var REGIONAL_CSS = `
11475
11302
  .pdp-regional {
11476
11303
  --rg-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -11548,7 +11375,7 @@ var REGIONAL_CSS = `
11548
11375
  font-size: 11px;
11549
11376
  letter-spacing: 0.12em;
11550
11377
  text-transform: uppercase;
11551
- color: color-mix(in srgb, var(--rg-primary) 75%, white 25%);
11378
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--rg-primary) 75%, white 25%));
11552
11379
  font-weight: 500;
11553
11380
  margin-bottom: 22px;
11554
11381
  }
@@ -11580,7 +11407,7 @@ var REGIONAL_CSS = `
11580
11407
  .pdp-regional-headline em {
11581
11408
  font-style: italic;
11582
11409
  font-variation-settings: 'SOFT' 100, 'WONK' 1;
11583
- color: var(--rg-primary);
11410
+ color: var(--syntro-pdp-primary-ink, var(--rg-primary));
11584
11411
  }
11585
11412
 
11586
11413
  .pdp-regional-body {
@@ -11604,7 +11431,7 @@ var REGIONAL_CSS = `
11604
11431
  font-family: var(--rg-display);
11605
11432
  font-size: 14.5px;
11606
11433
  font-variation-settings: 'SOFT' 70;
11607
- color: color-mix(in srgb, var(--rg-primary) 65%, white 35%);
11434
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--rg-primary) 65%, white 35%));
11608
11435
  line-height: 1.5;
11609
11436
  }
11610
11437
  .pdp-regional-footer-spark {
@@ -11650,7 +11477,7 @@ var REGIONAL_CSS = `
11650
11477
  to { opacity: 1; transform: translateY(0); }
11651
11478
  }
11652
11479
  `;
11653
- var PdpRegionalLit = class extends LitElement28 {
11480
+ var PdpRegionalLit = class extends LitElement29 {
11654
11481
  constructor() {
11655
11482
  super(...arguments);
11656
11483
  this.data = void 0;
@@ -11662,7 +11489,7 @@ var PdpRegionalLit = class extends LitElement28 {
11662
11489
  }
11663
11490
  connectedCallback() {
11664
11491
  super.connectedCallback();
11665
- ensureRegionalStyles();
11492
+ ensureStylesInRoot(this, "syntro-pdp-regional-style", REGIONAL_CSS);
11666
11493
  if (this._entering) this.requestUpdate();
11667
11494
  }
11668
11495
  disconnectedCallback() {
@@ -11686,11 +11513,11 @@ var PdpRegionalLit = class extends LitElement28 {
11686
11513
  }
11687
11514
  render() {
11688
11515
  if (!this.data) {
11689
- return html29`<div data-skeleton class="pdp-qa-skeleton" style="height:200px;">
11516
+ return html30`<div data-skeleton class="pdp-qa-skeleton" style="height:200px;">
11690
11517
  <div class="pdp-qa-skeleton-row" style="height:200px;border-radius:24px;"></div>
11691
11518
  </div>`;
11692
11519
  }
11693
- return html29`
11520
+ return html30`
11694
11521
  <section
11695
11522
  class="${this._entering ? "pdp-regional is-entering" : "pdp-regional"}"
11696
11523
  aria-label="Regional and temporal context"
@@ -11735,15 +11562,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-reg
11735
11562
  }
11736
11563
 
11737
11564
  // src/widgets/PdpPeerFeedLit.ts
11738
- import { html as html30, LitElement as LitElement29 } from "lit";
11739
- function ensurePeerFeedStyles() {
11740
- if (typeof document === "undefined") return;
11741
- if (document.getElementById("syntro-pdp-peer-feed-style")) return;
11742
- const s4 = document.createElement("style");
11743
- s4.id = "syntro-pdp-peer-feed-style";
11744
- s4.textContent = PEER_FEED_CSS;
11745
- document.head.appendChild(s4);
11746
- }
11565
+ import { html as html31, LitElement as LitElement30 } from "lit";
11747
11566
  var PEER_FEED_CSS = `
11748
11567
  .pdp-peer-feed {
11749
11568
  --pf-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -11859,14 +11678,14 @@ var PEER_FEED_CSS = `
11859
11678
  }
11860
11679
  .pdp-peer-quote::before {
11861
11680
  content: '\u201C ';
11862
- color: color-mix(in srgb, var(--pf-primary) 60%, transparent);
11681
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pf-primary) 60%, transparent));
11863
11682
  font-size: 1.4em;
11864
11683
  vertical-align: -0.2em;
11865
11684
  margin-right: -2px;
11866
11685
  }
11867
11686
  .pdp-peer-quote::after {
11868
11687
  content: ' \u201D';
11869
- color: color-mix(in srgb, var(--pf-primary) 60%, transparent);
11688
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pf-primary) 60%, transparent));
11870
11689
  font-size: 1.4em;
11871
11690
  vertical-align: -0.2em;
11872
11691
  margin-left: -2px;
@@ -11877,7 +11696,7 @@ var PEER_FEED_CSS = `
11877
11696
  align-items: center;
11878
11697
  gap: 7px;
11879
11698
  font-size: 12.5px;
11880
- color: color-mix(in srgb, var(--pf-primary) 75%, white 25%);
11699
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pf-primary) 75%, white 25%));
11881
11700
  font-style: italic;
11882
11701
  font-family: var(--pf-display);
11883
11702
  margin-top: 2px;
@@ -11947,7 +11766,7 @@ var PEER_FEED_CSS = `
11947
11766
  }
11948
11767
  }
11949
11768
  `;
11950
- var PdpPeerFeedLit = class extends LitElement29 {
11769
+ var PdpPeerFeedLit = class extends LitElement30 {
11951
11770
  constructor() {
11952
11771
  super(...arguments);
11953
11772
  this.data = void 0;
@@ -11959,7 +11778,7 @@ var PdpPeerFeedLit = class extends LitElement29 {
11959
11778
  }
11960
11779
  connectedCallback() {
11961
11780
  super.connectedCallback();
11962
- ensurePeerFeedStyles();
11781
+ ensureStylesInRoot(this, "syntro-pdp-peer-feed-style", PEER_FEED_CSS);
11963
11782
  if (this._entering) this.requestUpdate();
11964
11783
  }
11965
11784
  disconnectedCallback() {
@@ -11982,19 +11801,19 @@ var PdpPeerFeedLit = class extends LitElement29 {
11982
11801
  }
11983
11802
  render() {
11984
11803
  if (!this.data) {
11985
- return html30`<div data-skeleton class="pdp-peer-skeleton">
11804
+ return html31`<div data-skeleton class="pdp-peer-skeleton">
11986
11805
  <div class="pdp-peer-skeleton-row"></div>
11987
11806
  <div class="pdp-peer-skeleton-row"></div>
11988
11807
  <div class="pdp-peer-skeleton-row"></div>
11989
11808
  </div>`;
11990
11809
  }
11991
- return html30`
11810
+ return html31`
11992
11811
  <section
11993
11812
  class="${this._entering ? "pdp-peer-feed is-entering" : "pdp-peer-feed"}"
11994
11813
  aria-label="Users like you"
11995
11814
  >
11996
11815
  ${this.data.peers.map(
11997
- (peer) => html30`
11816
+ (peer) => html31`
11998
11817
  <article class="pdp-peer-post">
11999
11818
  <div class="pdp-peer-avatar" aria-hidden="true">${peer.initials}</div>
12000
11819
  <div class="pdp-peer-body">
@@ -12031,7 +11850,8 @@ var TAGS = {
12031
11850
  hero: "syntro-product-hero",
12032
11851
  grid: "syntro-product-grid",
12033
11852
  pdp: "syntro-pdp",
12034
- reco: "syntro-product-reco-carousel"
11853
+ reco: "syntro-product-reco-carousel",
11854
+ details: "syntro-product-details"
12035
11855
  };
12036
11856
  if (typeof customElements !== "undefined") {
12037
11857
  if (!customElements.get(TAGS.card)) customElements.define(TAGS.card, ProductCardLit);
@@ -12041,6 +11861,7 @@ if (typeof customElements !== "undefined") {
12041
11861
  if (!customElements.get(TAGS.grid)) customElements.define(TAGS.grid, ProductGridLit);
12042
11862
  if (!customElements.get(TAGS.pdp)) customElements.define(TAGS.pdp, SyntroPdpLit);
12043
11863
  if (!customElements.get(TAGS.reco)) customElements.define(TAGS.reco, ProductRecoCarouselLit);
11864
+ if (!customElements.get(TAGS.details)) customElements.define(TAGS.details, ProductDetailsLit);
12044
11865
  }
12045
11866
  if (typeof document !== "undefined") startNativeSlotWatcher();
12046
11867
  var _WIRED_RUNTIMES = /* @__PURE__ */ new WeakSet();
@@ -12081,17 +11902,21 @@ function onActivate(runtime2) {
12081
11902
  const observer2 = wirePdpElements(runtime2, document);
12082
11903
  return () => {
12083
11904
  observer2.disconnect();
12084
- const i2 = _ACTIVE_OBSERVERS.indexOf(observer2);
12085
- if (i2 >= 0) _ACTIVE_OBSERVERS.splice(i2, 1);
11905
+ const i = _ACTIVE_OBSERVERS.indexOf(observer2);
11906
+ if (i >= 0) _ACTIVE_OBSERVERS.splice(i, 1);
12086
11907
  _WIRED_RUNTIMES.delete(runtime2);
12087
11908
  };
12088
11909
  }
12089
- function makeMountable(tag) {
11910
+ function makeMountable(tag, hooks) {
12090
11911
  return {
12091
11912
  mount(container, config) {
12092
- const widgetProps = stripMountPlumbing(config ?? null);
11913
+ const widgetProps = resolveStorefrontProps(
11914
+ stripMountPlumbing(config ?? null),
11915
+ config?.runtime
11916
+ );
12093
11917
  const el = document.createElement(tag);
12094
11918
  el.props = widgetProps;
11919
+ hooks?.afterCreate?.(el, config);
12095
11920
  container.appendChild(el);
12096
11921
  return () => el.remove();
12097
11922
  }
@@ -12101,8 +11926,13 @@ var ProductCardMountable = makeMountable(TAGS.card);
12101
11926
  var ProductComparisonMountable = makeMountable(TAGS.comparison);
12102
11927
  var ProductHeroMountable = makeMountable(TAGS.hero);
12103
11928
  var ProductGridMountable = makeMountable(TAGS.grid);
12104
- var SyntroPdpMountable = makeMountable(TAGS.pdp);
11929
+ var SyntroPdpMountable = makeMountable(TAGS.pdp, {
11930
+ afterCreate: (el, config) => {
11931
+ el.runtimeRef = config?.runtime ?? null;
11932
+ }
11933
+ });
12105
11934
  var SyntroRecoMountable = makeMountable(TAGS.reco);
11935
+ var ProductDetailsMountable = makeMountable(TAGS.details);
12106
11936
  var runtime = {
12107
11937
  id: "adaptive-product",
12108
11938
  version: "1.0.0",
@@ -12185,6 +12015,15 @@ var runtime = {
12185
12015
  description: "Horizontally-scrollable carousel of recommended products with image, price, hook copy, and PDP links.",
12186
12016
  icon: "\u{1F3A0}"
12187
12017
  }
12018
+ },
12019
+ {
12020
+ id: "adaptive-product:details",
12021
+ component: ProductDetailsMountable,
12022
+ metadata: {
12023
+ name: "Product details",
12024
+ description: "The store's own product details",
12025
+ icon: "\u{1F4CB}"
12026
+ }
12188
12027
  }
12189
12028
  ]
12190
12029
  };
@@ -12201,35 +12040,8 @@ export {
12201
12040
  ProductGridMountable,
12202
12041
  SyntroPdpMountable,
12203
12042
  SyntroRecoMountable,
12043
+ ProductDetailsMountable,
12204
12044
  runtime,
12205
12045
  runtime_default
12206
12046
  };
12207
- /*! Bundled license information:
12208
-
12209
- @lit/context/lib/context-request-event.js:
12210
- @lit/context/lib/create-context.js:
12211
- @lit/context/lib/controllers/context-consumer.js:
12212
- @lit/context/lib/value-notifier.js:
12213
- @lit/context/lib/controllers/context-provider.js:
12214
- @lit/context/lib/context-root.js:
12215
- (**
12216
- * @license
12217
- * Copyright 2021 Google LLC
12218
- * SPDX-License-Identifier: BSD-3-Clause
12219
- *)
12220
-
12221
- @lit/context/lib/decorators/provide.js:
12222
- (**
12223
- * @license
12224
- * Copyright 2017 Google LLC
12225
- * SPDX-License-Identifier: BSD-3-Clause
12226
- *)
12227
-
12228
- @lit/context/lib/decorators/consume.js:
12229
- (**
12230
- * @license
12231
- * Copyright 2022 Google LLC
12232
- * SPDX-License-Identifier: BSD-3-Clause
12233
- *)
12234
- */
12235
- //# sourceMappingURL=chunk-QXR5X22R.js.map
12047
+ //# sourceMappingURL=chunk-QNZXBFP5.js.map