@syntrologie/adapt-product 2.55.1 → 2.57.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 (67) 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-E2PP64GL.js +5215 -0
  5. package/dist/chunk-E2PP64GL.js.map +7 -0
  6. package/dist/{chunk-QXR5X22R.js → chunk-YEW72FVW.js} +1038 -1214
  7. package/dist/chunk-YEW72FVW.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/imageStandin.d.ts +2 -1
  61. package/dist/widgets/imageStandin.d.ts.map +1 -1
  62. package/dist/widgets/presentationContext.d.ts +11 -0
  63. package/dist/widgets/presentationContext.d.ts.map +1 -0
  64. package/package.json +4 -7
  65. package/dist/chunk-D2W6UYLT.js +0 -370
  66. package/dist/chunk-D2W6UYLT.js.map +0 -7
  67. package/dist/chunk-QXR5X22R.js.map +0 -7
@@ -1,546 +1,25 @@
1
1
  import {
2
+ IMAGE_STANDIN_BACKGROUND,
2
3
  VISIBLE_FACT_KEYS,
4
+ ViewportClassController,
3
5
  __privateAdd,
4
6
  __privateGet,
5
7
  __privateMethod,
6
8
  __privateSet,
7
9
  cardSchema,
8
10
  comparisonSchema,
11
+ dispatchDiveDeeper,
12
+ external_exports,
13
+ fetchLivePrice,
9
14
  getProductPlatformAdapter,
10
15
  gridSchema,
11
16
  heroSchema,
12
17
  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
- };
18
+ standinInitial,
19
+ stripMountPlumbing,
20
+ takeoverPlanSchema,
21
+ watchLivePrice
22
+ } from "./chunk-E2PP64GL.js";
544
23
 
545
24
  // src/presets-store.ts
546
25
  function presetsWindow() {
@@ -577,9 +56,9 @@ function selectPoolItems(items, decision, max) {
577
56
  if (items.length <= max) return [...items];
578
57
  const sharp = decision && decision !== UNKNOWN_DECISION;
579
58
  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));
59
+ const matched = items.filter((i) => tagsOf(i).includes(decision));
60
+ const general = items.filter((i) => tagsOf(i).length === 0);
61
+ const rest = items.filter((i) => !matched.includes(i) && !general.includes(i));
583
62
  return [...matched, ...general, ...rest].slice(0, max);
584
63
  }
585
64
  const picked = [];
@@ -587,9 +66,9 @@ function selectPoolItems(items, decision, max) {
587
66
  for (const item of items) {
588
67
  const tags = tagsOf(item);
589
68
  if (tags.length === 0) continue;
590
- if (tags.some((t2) => covered.has(t2))) continue;
69
+ if (tags.some((t) => covered.has(t))) continue;
591
70
  picked.push(item);
592
- for (const t2 of tags) covered.add(t2);
71
+ for (const t of tags) covered.add(t);
593
72
  if (picked.length >= max) return picked.slice(0, max);
594
73
  }
595
74
  for (const item of items) {
@@ -1052,8 +531,8 @@ async function resolveScoredTemplate(opts) {
1052
531
  value = await opts.score(opts.scoreSpec);
1053
532
  }
1054
533
  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));
534
+ const setSlots = opts.slotIds.filter((s) => s in variant);
535
+ const unsetSlots = opts.slotIds.filter((s) => !(s in variant));
1057
536
  const pageOrder = opts.orderSlots ? opts.orderSlots([...opts.slotIds], (slot) => slot in variant) : opts.slotIds;
1058
537
  if (pageOrder.length > 0) {
1059
538
  opts.emitPlan(opts.buildPlan(pageOrder.map((slot) => ({ slot, preset: variant[slot] }))));
@@ -1068,6 +547,62 @@ async function resolveScoredTemplate(opts) {
1068
547
  return { value, decisionValue, setSlots, unsetSlots };
1069
548
  }
1070
549
 
550
+ // src/storefront-urls.ts
551
+ function storefrontBase(runtime2) {
552
+ if (!runtime2 || typeof runtime2 !== "object" || !("storefrontUrl" in runtime2)) return void 0;
553
+ if (typeof runtime2.storefrontUrl !== "string") return void 0;
554
+ try {
555
+ const base = new URL(runtime2.storefrontUrl);
556
+ return base.protocol === "https:" || base.protocol === "http:" ? base : void 0;
557
+ } catch {
558
+ return void 0;
559
+ }
560
+ }
561
+ function resolveAgainst(value, base) {
562
+ if (typeof value !== "string" || !value || /^(?:[a-z][a-z\d+.-]*:|\/\/|[?#])/i.test(value))
563
+ return value;
564
+ try {
565
+ const resolved = new URL(value, base);
566
+ return resolved.protocol === "https:" || resolved.protocol === "http:" ? resolved.href : value;
567
+ } catch {
568
+ return value;
569
+ }
570
+ }
571
+ function resolveStorePath(value, runtime2) {
572
+ const base = storefrontBase(runtime2);
573
+ return base ? resolveAgainst(value, base) : value;
574
+ }
575
+ function resolveStorefrontProps(props, runtime2) {
576
+ const base = storefrontBase(runtime2);
577
+ if (!base) return props;
578
+ const resolve = (value) => resolveAgainst(value, base);
579
+ const product = (value) => ({
580
+ ...value,
581
+ ...value.image ? { image: { ...value.image, src: resolve(value.image.src) } } : {},
582
+ ...Array.isArray(value.ctas) ? {
583
+ ctas: value.ctas.map((cta) => ({
584
+ ...cta,
585
+ ...cta.href ? { href: resolve(cta.href) } : {}
586
+ }))
587
+ } : {}
588
+ });
589
+ const content = props;
590
+ return {
591
+ ...props,
592
+ ...content.product ? { product: product(content.product) } : {},
593
+ ...Array.isArray(content.products) ? { products: content.products.map((value) => value ? product(value) : value) } : {},
594
+ ...Array.isArray(content.items) ? {
595
+ items: content.items.map(
596
+ (item) => item ? {
597
+ ...item,
598
+ ...item.image_url ? { image_url: resolve(item.image_url) } : {},
599
+ pdp_href: resolve(item.pdp_href)
600
+ } : item
601
+ )
602
+ } : {}
603
+ };
604
+ }
605
+
1071
606
  // src/takeover/resurrectionGuard.ts
1072
607
  function startResurrectionGuard(opts) {
1073
608
  const maxRetries = opts.maxRetries ?? 3;
@@ -1096,8 +631,8 @@ function startResurrectionGuard(opts) {
1096
631
  opts.onBudgetExhausted(exhausted.sectionId, exhausted.el);
1097
632
  return;
1098
633
  }
1099
- for (const { sectionId, el, n: n2 } of present) {
1100
- counts.set(sectionId, n2);
634
+ for (const { sectionId, el, n } of present) {
635
+ counts.set(sectionId, n);
1101
636
  opts.onResurrected(sectionId, el);
1102
637
  }
1103
638
  };
@@ -1154,7 +689,7 @@ var NOOP_DISPOSE = () => {
1154
689
  };
1155
690
  async function runTakeover(config, ports, opts = {}) {
1156
691
  if (!ports.hasSignals()) {
1157
- const declaredRoles = new Set(config.hostSections.map((s4) => s4.role));
692
+ const declaredRoles = new Set(config.hostSections.map((s) => s.role));
1158
693
  void Promise.resolve().then(() => ports.fetchPlan()).then(
1159
694
  (response2) => ports.renderModules(
1160
695
  {
@@ -1164,9 +699,9 @@ async function runTakeover(config, ports, opts = {}) {
1164
699
  // 1. host-derived entries (kind:sectionId) — review F2;
1165
700
  // 2. native kinds superseding a DECLARED host role (two-Q&As
1166
701
  // 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];
702
+ order: response2.plan.order.filter((entry) => {
703
+ if (entry.includes(":")) return false;
704
+ const superseded = KIND_SUPERSEDES_ROLE[entry];
1170
705
  return !(superseded && declaredRoles.has(superseded));
1171
706
  }),
1172
707
  suppress: []
@@ -1200,7 +735,7 @@ async function runTakeover(config, ports, opts = {}) {
1200
735
  return { outcome: "aborted-precommit", dispose: NOOP_DISPOSE };
1201
736
  }
1202
737
  const plan = response.plan;
1203
- const sectionsById = new Map(config.hostSections.map((s4) => [s4.id, s4]));
738
+ const sectionsById = new Map(config.hostSections.map((s) => [s.id, s]));
1204
739
  const toSuppress = [];
1205
740
  let reservedPx = 0;
1206
741
  for (const id of plan.suppress) {
@@ -1272,9 +807,8 @@ async function runTakeover(config, ports, opts = {}) {
1272
807
  }
1273
808
 
1274
809
  // src/takeover/fetchPlan.ts
1275
- import { z as z2 } from "zod";
1276
- var takeoverPlanResponseSchema = z2.object({
1277
- decisionArchetype: z2.string().min(1),
810
+ var takeoverPlanResponseSchema = external_exports.object({
811
+ decisionArchetype: external_exports.string().min(1),
1278
812
  plan: takeoverPlanSchema
1279
813
  }).strict();
1280
814
  function createTakeoverPlanFetcher(deps) {
@@ -1343,7 +877,7 @@ function applyRegionCurtain(region, opts = {}) {
1343
877
  return lift;
1344
878
  }
1345
879
  function applyCurtainToTargets(targets, opts = {}) {
1346
- const lifts = targets.map((t2) => applyRegionCurtain(t2, opts));
880
+ const lifts = targets.map((t) => applyRegionCurtain(t, opts));
1347
881
  return () => {
1348
882
  for (const lift of lifts) lift();
1349
883
  };
@@ -1398,14 +932,20 @@ function attachLifecycleDispose(result) {
1398
932
 
1399
933
  // src/widgets/PdpSectionHeaderLit.ts
1400
934
  import { html, LitElement, nothing } from "lit";
1401
- function ensureSectionHeaderStyles() {
935
+
936
+ // src/widgets/ensureStylesInRoot.ts
937
+ function ensureStylesInRoot(el, id, css) {
1402
938
  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);
939
+ const root = el.getRootNode();
940
+ const target = root instanceof ShadowRoot ? root : document.head;
941
+ if (target.querySelector(`#${id}`)) return;
942
+ const style = document.createElement("style");
943
+ style.id = id;
944
+ style.textContent = css;
945
+ target.appendChild(style);
1408
946
  }
947
+
948
+ // src/widgets/PdpSectionHeaderLit.ts
1409
949
  var SECTION_HEADER_CSS = `
1410
950
  /* The header renders at the FULL-WIDTH section (band) level so the eyebrow
1411
951
  can center across the whole horizontal slice, while the headline below
@@ -1530,7 +1070,7 @@ var PdpSectionHeaderLit = class extends LitElement {
1530
1070
  }
1531
1071
  connectedCallback() {
1532
1072
  super.connectedCallback();
1533
- ensureSectionHeaderStyles();
1073
+ ensureStylesInRoot(this, "syntro-pdp-section-header-style", SECTION_HEADER_CSS);
1534
1074
  }
1535
1075
  render() {
1536
1076
  const split = this.accent ? splitTitleAt(this.title, this.accent) : null;
@@ -1562,14 +1102,6 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-sec
1562
1102
 
1563
1103
  // src/widgets/PdpSlotLit.ts
1564
1104
  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
1105
  var SLOT_CSS = `
1574
1106
  .syntro-pdp-slot {
1575
1107
  --sl-primary: var(--syntro-pdp-primary, hsl(150 32% 38%));
@@ -1731,7 +1263,7 @@ var PdpSlotLit = class extends LitElement2 {
1731
1263
  }
1732
1264
  connectedCallback() {
1733
1265
  super.connectedCallback();
1734
- ensureSlotStyles();
1266
+ ensureStylesInRoot(this, "syntro-pdp-slot-style", SLOT_CSS);
1735
1267
  }
1736
1268
  willUpdate() {
1737
1269
  this.dataset.state = this.state;
@@ -1774,23 +1306,23 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-slo
1774
1306
  }
1775
1307
 
1776
1308
  // 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 };
1309
+ function parsePlanEntry(entry) {
1310
+ const i = entry.indexOf(":");
1311
+ if (i === -1) return { kind: entry, hostSectionId: null };
1312
+ return { kind: entry.slice(0, i), hostSectionId: entry.slice(i + 1) || null };
1781
1313
  }
1782
1314
  function createTakeoverModuleRenderer(deps) {
1783
1315
  return async (plan, onModuleFailed) => {
1784
1316
  const decisionArchetype = deps.getDecisionArchetype();
1785
1317
  const row = deps.getPresetRow(decisionArchetype) ?? {};
1786
1318
  const bakedWork = [];
1787
- plan.order.forEach((entry2, index) => {
1788
- const { kind, hostSectionId } = parsePlanEntry(entry2);
1319
+ plan.order.forEach((entry, index) => {
1320
+ const { kind, hostSectionId } = parsePlanEntry(entry);
1789
1321
  const slotId = `pdp-slot-${kind}`;
1790
1322
  const preset = row[slotId];
1791
1323
  const tag = deps.resolveTag(kind, preset?.variant ?? null);
1792
1324
  if (!tag) {
1793
- deps.telemetry("takeover.module_kind_unknown", { entry: entry2, kind });
1325
+ deps.telemetry("takeover.module_kind_unknown", { entry, kind });
1794
1326
  onModuleFailed(hostSectionId);
1795
1327
  return;
1796
1328
  }
@@ -1799,7 +1331,7 @@ function createTakeoverModuleRenderer(deps) {
1799
1331
  const section = document.createElement("section");
1800
1332
  section.toggleAttribute("data-syntro-pdp-section", true);
1801
1333
  section.setAttribute("data-slot", slotId);
1802
- section.setAttribute("data-syntro-takeover-entry", entry2);
1334
+ section.setAttribute("data-syntro-takeover-entry", entry);
1803
1335
  const header = document.createElement("syntro-pdp-section-header");
1804
1336
  header.eyebrow = task.header.eyebrow ?? "";
1805
1337
  header.title = task.header.title ?? "";
@@ -1820,7 +1352,7 @@ function createTakeoverModuleRenderer(deps) {
1820
1352
  } else {
1821
1353
  slotEl.state = "failed";
1822
1354
  }
1823
- deps.telemetry("takeover.module_failed", { entry: entry2, kind });
1355
+ deps.telemetry("takeover.module_failed", { entry, kind });
1824
1356
  onModuleFailed(hostSectionId);
1825
1357
  };
1826
1358
  const mountPayload = async (payload) => {
@@ -1857,14 +1389,6 @@ var RELATIONSHIP_LABELS = {
1857
1389
  complement: "Pairs with this",
1858
1390
  alternative: "Alternative"
1859
1391
  };
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
1392
  var OTHER_PRODUCT_TILE_CSS = `
1869
1393
  .opt-card {
1870
1394
  --opt-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -1909,7 +1433,7 @@ var OTHER_PRODUCT_TILE_CSS = `
1909
1433
  font-size: 10px;
1910
1434
  text-transform: uppercase;
1911
1435
  letter-spacing: 0.14em;
1912
- color: var(--opt-primary);
1436
+ color: var(--syntro-pdp-primary-ink, var(--opt-primary));
1913
1437
  font-weight: 600;
1914
1438
  display: flex;
1915
1439
  align-items: center;
@@ -1994,64 +1518,107 @@ var OTHER_PRODUCT_TILE_CSS = `
1994
1518
  background: color-mix(in srgb, var(--opt-primary) 10%, transparent);
1995
1519
  }
1996
1520
  `;
1521
+ var _unwatch, _OtherProductTileLit_instances, watchPrice_fn;
1997
1522
  var OtherProductTileLit = class extends LitElement3 {
1998
1523
  constructor() {
1999
1524
  super(...arguments);
1525
+ __privateAdd(this, _OtherProductTileLit_instances);
2000
1526
  this.tile = null;
1527
+ /** The store's live display price, painted over the catalog price. */
1528
+ this._livePrice = null;
1529
+ __privateAdd(this, _unwatch, null);
1530
+ /**
1531
+ * View asks whoever holds the page to open this product. The event crosses
1532
+ * shadow roots, so a canvas that mounts the page can answer it; on a page
1533
+ * where nothing listens, the button does nothing.
1534
+ */
1535
+ this._onView = () => {
1536
+ if (!this.tile) return;
1537
+ const { recommended_product_id: productId, name, image_url, price_cents } = this.tile;
1538
+ const price = this._livePrice ?? this.tile.price;
1539
+ this.dispatchEvent(
1540
+ new CustomEvent("syntro:view-product", {
1541
+ bubbles: true,
1542
+ composed: true,
1543
+ detail: { productId, name, image_url, price, price_cents }
1544
+ })
1545
+ );
1546
+ };
1547
+ }
1548
+ willUpdate(changed) {
1549
+ if (!changed.has("tile")) return;
1550
+ this._livePrice = null;
1551
+ if (this.isConnected) __privateMethod(this, _OtherProductTileLit_instances, watchPrice_fn).call(this);
1552
+ }
1553
+ disconnectedCallback() {
1554
+ var _a;
1555
+ super.disconnectedCallback();
1556
+ (_a = __privateGet(this, _unwatch)) == null ? void 0 : _a.call(this);
1557
+ __privateSet(this, _unwatch, null);
2001
1558
  }
2002
1559
  createRenderRoot() {
2003
1560
  return this;
2004
1561
  }
2005
1562
  connectedCallback() {
2006
1563
  super.connectedCallback();
2007
- ensureOtherProductTileStyles();
1564
+ ensureStylesInRoot(this, "syntro-other-product-tile-style", OTHER_PRODUCT_TILE_CSS);
1565
+ __privateMethod(this, _OtherProductTileLit_instances, watchPrice_fn).call(this);
2008
1566
  }
2009
1567
  render() {
2010
- const t2 = this.tile;
2011
- if (!t2) {
1568
+ const t = this.tile;
1569
+ if (!t) {
2012
1570
  return html3``;
2013
1571
  }
2014
- const relationshipLabel = RELATIONSHIP_LABELS[t2.relationship];
2015
- const price = t2.price_cents == null ? "" : `$${(t2.price_cents / 100).toFixed(0)}`;
1572
+ const relationshipLabel = RELATIONSHIP_LABELS[t.relationship];
1573
+ const price = this._livePrice ?? t.price ?? "";
2016
1574
  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" />
1575
+ <article class="opt-card" data-relationship="${t.relationship}">
1576
+ ${t.image_url ? html3`<div class="opt-image">
1577
+ <img src="${t.image_url}" alt="${t.name}" loading="lazy" />
2020
1578
  </div>` : null}
2021
- <div class="opt-relationship" data-relationship="${t2.relationship}">
1579
+ <div class="opt-relationship" data-relationship="${t.relationship}">
2022
1580
  ${relationshipLabel ? html3`${relationshipLabel}` : null}
2023
- <span class="opt-relationship-type">${t2.relationship}</span>
1581
+ <span class="opt-relationship-type">${t.relationship}</span>
2024
1582
  </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>`)}
1583
+ <h3 class="opt-name">${t.name}</h3>
1584
+ ${t.tagline ? html3`<p class="opt-tagline">${t.tagline}</p>` : null}
1585
+ ${t.why_blurb ? html3`<p class="opt-why">${t.why_blurb}</p>` : null}
1586
+ ${t.evidence_chips.length ? html3`<div class="opt-chips">
1587
+ ${t.evidence_chips.map((c) => html3`<span class="opt-chip">${c.display}</span>`)}
2030
1588
  </div>` : null}
2031
1589
  <div class="opt-footer">
2032
1590
  <span class="opt-price">${price}</span>
2033
- <button type="button" class="opt-cta" data-action="view-product">View</button>
1591
+ <button type="button" class="opt-cta" data-action="view-product" @click=${this._onView}>
1592
+ View
1593
+ </button>
2034
1594
  </div>
2035
1595
  </article>
2036
1596
  `;
2037
1597
  }
2038
1598
  };
1599
+ _unwatch = new WeakMap();
1600
+ _OtherProductTileLit_instances = new WeakSet();
1601
+ /** The store's live price, now and whenever the runtime publishes a
1602
+ * commerce adapter; no answer keeps the catalog price the server sent. */
1603
+ watchPrice_fn = function() {
1604
+ var _a;
1605
+ (_a = __privateGet(this, _unwatch)) == null ? void 0 : _a.call(this);
1606
+ __privateSet(this, _unwatch, null);
1607
+ const id = this.tile?.recommended_product_id;
1608
+ if (!id || !this.tile?.price) return;
1609
+ __privateSet(this, _unwatch, watchLivePrice(id, (live) => {
1610
+ if (this.tile?.recommended_product_id === id) this._livePrice = live.price;
1611
+ }));
1612
+ };
2039
1613
  OtherProductTileLit.properties = {
2040
- tile: { attribute: false }
1614
+ tile: { attribute: false },
1615
+ _livePrice: { state: true }
2041
1616
  };
2042
1617
  if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-other-product-tile")) {
2043
1618
  customElements.define("syntro-pdp-other-product-tile", OtherProductTileLit);
2044
1619
  }
2045
1620
 
2046
1621
  // 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
1622
  var CAROUSEL_CSS = `
2056
1623
  /* Host containment \u2014 must not bust the PDP container width. */
2057
1624
  syntro-pdp-other-products-carousel {
@@ -2125,7 +1692,7 @@ var PdpOtherProductsCarouselLit = class extends LitElement4 {
2125
1692
  }
2126
1693
  connectedCallback() {
2127
1694
  super.connectedCallback();
2128
- ensureCarouselStyles();
1695
+ ensureStylesInRoot(this, "syntro-pdp-other-products-carousel-style", CAROUSEL_CSS);
2129
1696
  }
2130
1697
  render() {
2131
1698
  if (!this.data) return html4``;
@@ -2153,14 +1720,6 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-oth
2153
1720
 
2154
1721
  // src/widgets/PdpOtherProductsGridLit.ts
2155
1722
  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
1723
  var GRID_CSS = `
2165
1724
  syntro-pdp-other-products-grid {
2166
1725
  display: block;
@@ -2197,7 +1756,7 @@ var PdpOtherProductsGridLit = class extends LitElement5 {
2197
1756
  }
2198
1757
  connectedCallback() {
2199
1758
  super.connectedCallback();
2200
- ensureGridStyles();
1759
+ ensureStylesInRoot(this, "syntro-pdp-other-products-grid-style", GRID_CSS);
2201
1760
  }
2202
1761
  render() {
2203
1762
  if (!this.data) return html5``;
@@ -2235,7 +1794,7 @@ function _readPdpDecisionArchetypes() {
2235
1794
  if (typeof window === "undefined") return void 0;
2236
1795
  return window.__SYNTRO_PDP_DECISION_ARCHETYPES__;
2237
1796
  }
2238
- async function composeModule(authedFetch, args) {
1797
+ async function composeModule(authedFetch, args, signal) {
2239
1798
  try {
2240
1799
  const requestBody = {
2241
1800
  product_name: args.productName,
@@ -2254,7 +1813,8 @@ async function composeModule(authedFetch, args) {
2254
1813
  };
2255
1814
  const resp = await authedFetch("/api/pdp/module", {
2256
1815
  method: "POST",
2257
- body: JSON.stringify(requestBody)
1816
+ body: JSON.stringify(requestBody),
1817
+ signal
2258
1818
  });
2259
1819
  if (!resp.ok) return null;
2260
1820
  const data = await resp.json();
@@ -2292,33 +1852,33 @@ function _titleCase(slug) {
2292
1852
  return slug.split(/[-_\s]+/).map((w) => w ? w[0].toUpperCase() + w.slice(1) : "").join(" ");
2293
1853
  }
2294
1854
  function _countSignals(signals) {
2295
- let n2 = 0;
1855
+ let n = 0;
2296
1856
  const walk = (v) => {
2297
1857
  if (v && typeof v === "object") {
2298
1858
  for (const x of Object.values(v)) walk(x);
2299
1859
  } else if (v !== "" && v != null) {
2300
- n2++;
1860
+ n++;
2301
1861
  }
2302
1862
  };
2303
1863
  walk(signals);
2304
- return n2;
1864
+ return n;
2305
1865
  }
2306
1866
  function _signalDimensions(signals) {
2307
- let n2 = 0;
1867
+ let n = 0;
2308
1868
  for (const v of Object.values(signals)) {
2309
1869
  if (v && typeof v === "object") {
2310
- if (Object.keys(v).length) n2++;
1870
+ if (Object.keys(v).length) n++;
2311
1871
  } else if (v !== "" && v != null && v !== false) {
2312
- n2++;
1872
+ n++;
2313
1873
  }
2314
1874
  }
2315
- return n2;
1875
+ return n;
2316
1876
  }
2317
1877
  function _fingerprint(signals) {
2318
- const s4 = JSON.stringify(signals);
1878
+ const s = JSON.stringify(signals);
2319
1879
  let h = 2166136261;
2320
- for (let i2 = 0; i2 < s4.length; i2++) {
2321
- h ^= s4.charCodeAt(i2);
1880
+ for (let i = 0; i < s.length; i++) {
1881
+ h ^= s.charCodeAt(i);
2322
1882
  h = Math.imul(h, 16777619);
2323
1883
  }
2324
1884
  return (h >>> 0).toString(16).padStart(8, "0");
@@ -2416,9 +1976,9 @@ function buildExplain(opts) {
2416
1976
  dossier
2417
1977
  };
2418
1978
  }
2419
- async function fetchDossier(authedFetch) {
1979
+ async function fetchDossier(authedFetch, signal) {
2420
1980
  try {
2421
- const resp = await authedFetch("/api/pdp/dossier", { method: "POST" });
1981
+ const resp = await authedFetch("/api/pdp/dossier", { method: "POST", signal });
2422
1982
  if (!resp.ok) return [];
2423
1983
  const data = await resp.json();
2424
1984
  const rows = data.metrics;
@@ -2558,8 +2118,8 @@ var _PAGE_TRAIL_MAX_CHARS = 600;
2558
2118
  function _renderPageTrail(history) {
2559
2119
  const paths = [];
2560
2120
  let length = 0;
2561
- for (let i2 = history.length - 1; i2 >= 0; i2--) {
2562
- let path = history[i2].url;
2121
+ for (let i = history.length - 1; i >= 0; i--) {
2122
+ let path = history[i].url;
2563
2123
  try {
2564
2124
  const u = new URL(path);
2565
2125
  path = `${u.pathname}${u.search}`;
@@ -2572,14 +2132,6 @@ function _renderPageTrail(history) {
2572
2132
  }
2573
2133
  return paths.join(" \u2192 ");
2574
2134
  }
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
2135
  var SECTION_LAYOUT_CSS = `
2584
2136
  [data-syntro-pdp-root] {
2585
2137
  --sc-pdp-max-w: var(--syntro-pdp-max-width, 1080px);
@@ -2760,6 +2312,7 @@ function resolveTagForMount(inst) {
2760
2312
  }
2761
2313
  return null;
2762
2314
  }
2315
+ var _abort;
2763
2316
  var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2764
2317
  constructor() {
2765
2318
  super(...arguments);
@@ -2840,6 +2393,15 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2840
2393
  * after reconnect must undo that before the engine renders again. */
2841
2394
  this._takeoverRanBefore = false;
2842
2395
  this._takeoverLateWarned = false;
2396
+ /**
2397
+ * Cancels in-flight /api/pdp/module + /api/pdp/dossier calls when the
2398
+ * element is removed mid-request — a canvas card can be torn down while a
2399
+ * fetch is still pending, and an unaborted response landing after teardown
2400
+ * would apply a mutation/mount to a disconnected element. Reset to a fresh
2401
+ * controller on (re)connect so a re-added element gets a live signal
2402
+ * instead of one that's already aborted from the previous lifecycle.
2403
+ */
2404
+ __privateAdd(this, _abort, new AbortController());
2843
2405
  /**
2844
2406
  * bfcache re-entry (review F4). pagehide disposes the run
2845
2407
  * (attachLifecycleDispose) but a bfcache restore does NOT re-fire
@@ -2872,8 +2434,8 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2872
2434
  warn: (code, props) => this._warnDecision(code, props),
2873
2435
  cleanup: () => this._cleanupDecisionEffects()
2874
2436
  });
2875
- this._onDossierToggle = (e2) => {
2876
- const open = e2.target.open;
2437
+ this._onDossierToggle = (e) => {
2438
+ const open = e.target.open;
2877
2439
  this._dispatchDecision({ type: "dossier-toggled", open });
2878
2440
  };
2879
2441
  /** Stamps data-viewport on this tag (central classification,
@@ -2909,6 +2471,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2909
2471
  return this;
2910
2472
  }
2911
2473
  connectedCallback() {
2474
+ if (__privateGet(this, _abort).signal.aborted) __privateSet(this, _abort, new AbortController());
2912
2475
  if (!this._takeoverModeLatched) {
2913
2476
  this._takeoverModeLatched = true;
2914
2477
  this._takeoverMode = Boolean(this.takeover);
@@ -2920,7 +2483,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2920
2483
  }
2921
2484
  if (this._takeoverMode) {
2922
2485
  super.connectedCallback();
2923
- ensureSectionLayoutStyles();
2486
+ ensureStylesInRoot(this, "syntro-pdp-layout-style", SECTION_LAYOUT_CSS);
2924
2487
  if (typeof window !== "undefined") {
2925
2488
  window.removeEventListener("pageshow", this._onTakeoverPageShow);
2926
2489
  window.addEventListener("pageshow", this._onTakeoverPageShow);
@@ -2930,7 +2493,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2930
2493
  }
2931
2494
  const reconnecting = this._decisionState.connection === "disconnected";
2932
2495
  super.connectedCallback();
2933
- ensureSectionLayoutStyles();
2496
+ ensureStylesInRoot(this, "syntro-pdp-layout-style", SECTION_LAYOUT_CSS);
2934
2497
  if (!this.productId) this._selfIdentify = true;
2935
2498
  this._refreshNativeSlots();
2936
2499
  this._syncDecisionRuntime(reconnecting);
@@ -2944,6 +2507,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
2944
2507
  }
2945
2508
  }
2946
2509
  disconnectedCallback() {
2510
+ __privateGet(this, _abort).abort();
2947
2511
  if (this._takeoverMode) {
2948
2512
  super.disconnectedCallback();
2949
2513
  if (typeof window !== "undefined") {
@@ -3145,24 +2709,28 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3145
2709
  }),
3146
2710
  requestLiveModule: async (kind, decisionArchetype) => {
3147
2711
  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] ?? ""
2712
+ return composeModule(
2713
+ authedFetch,
2714
+ {
2715
+ productName: this.productName,
2716
+ // Guaranteed non-empty: _startTakeover gates on a missing product id.
2717
+ productId: this.productId,
2718
+ kind,
2719
+ variant: null,
2720
+ // The takeover round trip scores only the decision axis (the server
2721
+ // resolves the plan from it); the sport-archetype axis is unscored
2722
+ // on this path, so it is sent as an explicit 'unknown' — never a
2723
+ // silently-invented value.
2724
+ archetype: { slug: "unknown", description: "" },
2725
+ decisionArchetype: decisionArchetype === "unknown" ? null : {
2726
+ slug: decisionArchetype,
2727
+ description: decisionDescriptions[decisionArchetype] ?? ""
2728
+ },
2729
+ behaviorSummary: "",
2730
+ signals
3162
2731
  },
3163
- behaviorSummary: "",
3164
- signals
3165
- });
2732
+ __privateGet(this, _abort).signal
2733
+ );
3166
2734
  },
3167
2735
  telemetry: telemetry2,
3168
2736
  getDecisionArchetype: () => scored.decisionArchetype
@@ -3329,13 +2897,35 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3329
2897
  }
3330
2898
  container.innerHTML = "";
3331
2899
  const el = document.createElement(tag);
3332
- el.data = inst.content;
2900
+ el.data = this._withStoreImages(tag, inst.content);
3333
2901
  container.appendChild(el);
3334
2902
  this._mountDurations.set(slotId, Math.round(performance.now() - slotStartedAt));
3335
2903
  outcomes.push({ status: "mounted", slot: slotId });
3336
2904
  }
3337
2905
  return outcomes;
3338
2906
  }
2907
+ /**
2908
+ * The cross-sell tiles' images are store-relative catalog paths. Inside an
2909
+ * app host's card the document is the host's sandbox and <base> is blocked,
2910
+ * so each is resolved against the runtime's storefront before the carousel
2911
+ * or grid gets it. On a storefront page there is no storefrontUrl and
2912
+ * nothing changes.
2913
+ */
2914
+ _withStoreImages(tag, content) {
2915
+ if (tag !== "syntro-pdp-other-products-carousel" && tag !== "syntro-pdp-other-products-grid")
2916
+ return content;
2917
+ const tiles = content?.tiles;
2918
+ if (!Array.isArray(tiles)) return content;
2919
+ return {
2920
+ ...content,
2921
+ tiles: tiles.map(
2922
+ (tile) => tile && typeof tile === "object" && "image_url" in tile ? {
2923
+ ...tile,
2924
+ image_url: resolveStorePath(tile.image_url, this.runtimeRef)
2925
+ } : tile
2926
+ )
2927
+ };
2928
+ }
3339
2929
  /** Render from config: score the archetype → mount the preset set-values. */
3340
2930
  async _fireFromConfig(productPresets, score, authedFetch, generation, run, scoreMulti) {
3341
2931
  const startedAt = performance.now();
@@ -3353,7 +2943,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3353
2943
  let scoredArgs = null;
3354
2944
  const composeAdapter = createPdpComposeAdapter({
3355
2945
  scoreSummary: () => authedFetch ? score({ model_id: "summary", name: "behavior_summary", type: "string", signals }) : Promise.resolve("unknown"),
3356
- requestDossier: () => authedFetch ? fetchDossier(authedFetch) : Promise.resolve([]),
2946
+ requestDossier: () => authedFetch ? fetchDossier(authedFetch, __privateGet(this, _abort).signal) : Promise.resolve([]),
3357
2947
  resolveTemplate: (_currentGeneration, composeUnset) => resolveScoredTemplate({
3358
2948
  // Wrap the scorer so the resolved SPORT archetype lands on the
3359
2949
  // element the instant it scores — the native-takeover check needs it
@@ -3441,22 +3031,26 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3441
3031
  requestModule: async ({ slot, archetype, decisionArchetype, behaviorSummary }) => {
3442
3032
  if (!authedFetch || !run.isCurrent()) return;
3443
3033
  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] ?? ""
3034
+ const payload = await composeModule(
3035
+ authedFetch,
3036
+ {
3037
+ productName: this.productName,
3038
+ productId: this.productId || null,
3039
+ kind,
3040
+ variant: null,
3041
+ archetype: {
3042
+ slug: archetype,
3043
+ description: archetypeDescriptions[archetype] ?? ""
3044
+ },
3045
+ decisionArchetype: decisionArchetype === "unknown" ? null : {
3046
+ slug: decisionArchetype,
3047
+ description: decisionDescriptions[decisionArchetype] ?? ""
3048
+ },
3049
+ behaviorSummary,
3050
+ signals
3452
3051
  },
3453
- decisionArchetype: decisionArchetype === "unknown" ? null : {
3454
- slug: decisionArchetype,
3455
- description: decisionDescriptions[decisionArchetype] ?? ""
3456
- },
3457
- behaviorSummary,
3458
- signals
3459
- });
3052
+ __privateGet(this, _abort).signal
3053
+ );
3460
3054
  if (!run.isCurrent()) return;
3461
3055
  if (!payload) {
3462
3056
  run.dispatch({ type: "module-failed", generation, slot });
@@ -3582,7 +3176,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3582
3176
  _renderWhyBox(view) {
3583
3177
  if (view.status === "hidden") return "";
3584
3178
  if (view.status === "loading") return this._renderWhyBoxLoading();
3585
- const e2 = view.explanation;
3179
+ const e = view.explanation;
3586
3180
  return html6`
3587
3181
  <style>
3588
3182
  /* Token bridge: every color resolves from a --syntro-pdp-* var the
@@ -3624,7 +3218,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3624
3218
  [data-syntro-pdp-why][open] > summary { margin-bottom: 6px; }
3625
3219
  [data-syntro-pdp-why-note] { font-size: 11px; color: var(--syntro-pdp-muted, #666); margin: 0 0 4px; }
3626
3220
  [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); }
3221
+ [data-syntro-pdp-why-archetype] strong { color: var(--syntro-pdp-primary-ink, var(--syntro-pdp-primary, #1a1a1a)); }
3628
3222
  [data-syntro-pdp-why-summary] {
3629
3223
  font-size: 12.5px; font-style: italic; color: var(--syntro-pdp-muted, #666); margin: 6px 0 8px;
3630
3224
  border-left: 2px solid var(--syntro-pdp-accent, #555); padding-left: 10px;
@@ -3670,14 +3264,14 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3670
3264
  Shown in demo mode only — a peek under the hood at the live personalization.
3671
3265
  </div>
3672
3266
  <div data-syntro-pdp-why-archetype>
3673
- Shopping as: <strong>${e2.archetypeLabel}</strong>${e2.archetypeDesc ? html6` — ${e2.archetypeDesc}` : ""}
3267
+ Shopping as: <strong>${e.archetypeLabel}</strong>${e.archetypeDesc ? html6` — ${e.archetypeDesc}` : ""}
3674
3268
  </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}”` : ""}
3269
+ ${e.decisionLabel ? html6`<div data-syntro-pdp-why-archetype>
3270
+ What they're weighing: <strong>${e.decisionLabel}</strong>${e.decisionDesc ? html6` — “${e.decisionDesc}”` : ""}
3677
3271
  </div>` : ""}
3678
- ${e2.summary ? html6`<div data-syntro-pdp-why-summary>"${e2.summary}"</div>` : ""}
3272
+ ${e.summary ? html6`<div data-syntro-pdp-why-summary>"${e.summary}"</div>` : ""}
3679
3273
  <div data-syntro-pdp-why-metrics>
3680
- ${e2.metrics.map(
3274
+ ${e.metrics.map(
3681
3275
  (m) => html6`<div data-syntro-pdp-why-metric>
3682
3276
  <span data-syntro-pdp-why-metric-value>${m.value}</span>
3683
3277
  <span data-syntro-pdp-why-metric-label>${m.label}</span>
@@ -3760,10 +3354,10 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3760
3354
  ${skeletonStyles}
3761
3355
  <div data-syntro-pdp-root data-state="pending" data-syntro-pdp-skeleton>
3762
3356
  ${[0, 1, 2].map(
3763
- (i2) => html6`
3357
+ (i) => html6`
3764
3358
  <section
3765
3359
  data-syntro-pdp-skeleton-section
3766
- style="animation-delay: ${i2 * 0.15}s"
3360
+ style="animation-delay: ${i * 0.15}s"
3767
3361
  >
3768
3362
  <div data-syntro-pdp-skeleton-header>
3769
3363
  <div data-syntro-pdp-skeleton-eyebrow></div>
@@ -3820,6 +3414,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement6 {
3820
3414
  `;
3821
3415
  }
3822
3416
  };
3417
+ _abort = new WeakMap();
3823
3418
  _SyntroPdpLit.properties = {
3824
3419
  productId: { type: String, attribute: "product-id" },
3825
3420
  productName: { type: String, attribute: "product-name" },
@@ -3833,9 +3428,9 @@ _SyntroPdpLit.properties = {
3833
3428
  /**
3834
3429
  * Self-contained skeleton (shimmer) CSS. Rendered inside the component
3835
3430
  * 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.
3431
+ * per-slot accent rules live in `SECTION_LAYOUT_CSS`, injected once per
3432
+ * root via `ensureStylesInRoot`, not here, so they're emitted once per
3433
+ * root rather than once per `<syntro-pdp>` instance.
3839
3434
  */
3840
3435
  _SyntroPdpLit._skeletonStyles = html6`
3841
3436
  <style>
@@ -4258,14 +3853,14 @@ function collectFromNode(node) {
4258
3853
  const containers = [];
4259
3854
  if (node.matches(`[${NATIVE_SLOT_ATTR}]`)) containers.push(node);
4260
3855
  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);
3856
+ for (const c of containers) {
3857
+ if (c.closest("syntro-pdp")) continue;
3858
+ const pid = nativeProductId(c);
4264
3859
  if (pid) pending.add(pid);
4265
3860
  }
4266
3861
  }
4267
- var onDemoToggle = (e2) => {
4268
- const detail = e2.detail;
3862
+ var onDemoToggle = (e) => {
3863
+ const detail = e.detail;
4269
3864
  if (!detail || typeof detail.on !== "boolean") return;
4270
3865
  toggleOn = detail.on;
4271
3866
  renderSession();
@@ -4341,9 +3936,9 @@ async function resolveDomSelectorImage(input, _signal) {
4341
3936
  // src/bind/jsonld.ts
4342
3937
  function isProduct(node) {
4343
3938
  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");
3939
+ const t = node["@type"];
3940
+ if (typeof t === "string") return t === "Product";
3941
+ if (Array.isArray(t)) return t.includes("Product");
4347
3942
  return false;
4348
3943
  }
4349
3944
  function parseScripts(scripts) {
@@ -4358,7 +3953,7 @@ function parseScripts(scripts) {
4358
3953
  while (stack.length) {
4359
3954
  const cur = stack.pop();
4360
3955
  if (Array.isArray(cur)) {
4361
- for (const c2 of cur) stack.push(c2);
3956
+ for (const c of cur) stack.push(c);
4362
3957
  continue;
4363
3958
  }
4364
3959
  if (isProduct(cur)) return cur;
@@ -4563,14 +4158,15 @@ async function resolveImageBind(input, signal) {
4563
4158
  }
4564
4159
 
4565
4160
  // src/bind/controller.ts
4566
- var _abort, _pending, _destroyed, _BindController_instances, resolveImage_fn, resolvePrice_fn;
4161
+ var _abort2, _pending, _unwatch2, _destroyed, _BindController_instances, resolveImage_fn, resolvePrice_fn;
4567
4162
  var BindController = class {
4568
4163
  constructor(products, callback) {
4569
4164
  this.products = products;
4570
4165
  this.callback = callback;
4571
4166
  __privateAdd(this, _BindController_instances);
4572
- __privateAdd(this, _abort, new AbortController());
4167
+ __privateAdd(this, _abort2, new AbortController());
4573
4168
  __privateAdd(this, _pending, []);
4169
+ __privateAdd(this, _unwatch2, []);
4574
4170
  __privateAdd(this, _destroyed, false);
4575
4171
  }
4576
4172
  start() {
@@ -4580,6 +4176,15 @@ var BindController = class {
4580
4176
  }
4581
4177
  if (product.price?.bind) {
4582
4178
  __privateGet(this, _pending).push(__privateMethod(this, _BindController_instances, resolvePrice_fn).call(this, product.id, product.price.bind));
4179
+ } else if (product.price || product.salePrice) {
4180
+ __privateGet(this, _unwatch2).push(
4181
+ watchLivePrice(product.id, (live) => {
4182
+ if (__privateGet(this, _destroyed)) return;
4183
+ this.callback(product.id, "price", live.price);
4184
+ this.callback(product.id, "compare_at", live.compareAt);
4185
+ })
4186
+ );
4187
+ __privateGet(this, _pending).push(fetchLivePrice(product.id).then(() => void 0));
4583
4188
  }
4584
4189
  }
4585
4190
  }
@@ -4589,23 +4194,42 @@ var BindController = class {
4589
4194
  destroy() {
4590
4195
  if (__privateGet(this, _destroyed)) return;
4591
4196
  __privateSet(this, _destroyed, true);
4592
- __privateGet(this, _abort).abort();
4197
+ __privateGet(this, _abort2).abort();
4198
+ for (const stop of __privateGet(this, _unwatch2)) stop();
4199
+ __privateSet(this, _unwatch2, []);
4593
4200
  }
4594
4201
  };
4595
- _abort = new WeakMap();
4202
+ _abort2 = new WeakMap();
4596
4203
  _pending = new WeakMap();
4204
+ _unwatch2 = new WeakMap();
4597
4205
  _destroyed = new WeakMap();
4598
4206
  _BindController_instances = new WeakSet();
4599
4207
  resolveImage_fn = async function(productId, bind) {
4600
- const value = await resolveImageBind(bind, __privateGet(this, _abort).signal);
4208
+ const value = await resolveImageBind(bind, __privateGet(this, _abort2).signal);
4601
4209
  if (__privateGet(this, _destroyed)) return;
4602
4210
  this.callback(productId, "image", value);
4603
4211
  };
4604
4212
  resolvePrice_fn = async function(productId, bind) {
4605
- const value = await resolvePriceBind(bind, __privateGet(this, _abort).signal);
4213
+ const value = await resolvePriceBind(bind, __privateGet(this, _abort2).signal);
4606
4214
  if (__privateGet(this, _destroyed)) return;
4607
4215
  this.callback(productId, "price", value);
4608
4216
  };
4217
+ function mergeResolved(resolved, productId, kind, value) {
4218
+ if (kind === "compare_at") {
4219
+ return { ...resolved, [productId]: { ...resolved[productId], compareAt: value } };
4220
+ }
4221
+ if (value == null) return resolved;
4222
+ return { ...resolved, [productId]: { ...resolved[productId], [kind]: value } };
4223
+ }
4224
+ function keepResolved(resolved, products) {
4225
+ const kept = {};
4226
+ for (const product of products) {
4227
+ const entry = resolved[product.id];
4228
+ if (!entry || product.price?.bind || !("compareAt" in entry)) continue;
4229
+ kept[product.id] = { price: entry.price, compareAt: entry.compareAt };
4230
+ }
4231
+ return kept;
4232
+ }
4609
4233
 
4610
4234
  // src/decision/card.ts
4611
4235
  var VARIANT_ERROR_COPY = {
@@ -4627,9 +4251,11 @@ function createInitialCardState(generation = 0) {
4627
4251
  variants: { status: "idle" },
4628
4252
  cart: { status: "idle" },
4629
4253
  selection: {},
4630
- activation: { status: "idle" }
4254
+ activation: { status: "idle" },
4255
+ optionsPinned: false
4631
4256
  };
4632
4257
  }
4258
+ var optionsOpen = (s) => s.face === "back" || s.optionsPinned;
4633
4259
  function selectCardView(state) {
4634
4260
  const primaryCta = state.cart.status === "added" ? {
4635
4261
  mode: "view-cart",
@@ -4639,7 +4265,7 @@ function selectCardView(state) {
4639
4265
  disabled: state.connection !== "connected" || state.commerce !== "available" || state.product.status !== "valid"
4640
4266
  };
4641
4267
  let panel = { status: "hidden", payload: null, error: null, retryable: false };
4642
- if (state.face === "back") {
4268
+ if (optionsOpen(state)) {
4643
4269
  switch (state.variants.status) {
4644
4270
  case "ready":
4645
4271
  panel = { status: "ready", payload: state.variants.payload, error: null, retryable: false };
@@ -4684,7 +4310,11 @@ function replaceProps(state, event) {
4684
4310
  return { state, effects: effects2 };
4685
4311
  }
4686
4312
  const generation = state.generation + 1;
4687
- const next = { ...createInitialCardState(generation), commerce: state.commerce };
4313
+ const next = {
4314
+ ...createInitialCardState(generation),
4315
+ commerce: state.commerce,
4316
+ optionsPinned: state.optionsPinned
4317
+ };
4688
4318
  const effects = [{ type: "cancel-flip-settle" }];
4689
4319
  if (!event.valid || event.productId === null) {
4690
4320
  return {
@@ -4717,7 +4347,13 @@ function activatePrimary(state, event) {
4717
4347
  // CTA is a plain navigating <a>, never dispatched here) — but the state
4718
4348
  // machine itself must also refuse to start the Woo/Shopify variant-
4719
4349
  // lookup flow for a product that has none.
4720
- !state.product.commerceCapable || state.face !== "front") {
4350
+ !state.product.commerceCapable || state.face !== "front" || // A pinned (double-wide) card has no primary CTA to activate — Add to
4351
+ // cart lives in the options panel beside the front face. Without this
4352
+ // guard a stray primary-activated dispatch after an add (cart status
4353
+ // 'added', face still 'front') would fall into the view-cart branch below
4354
+ // and emit close-sdk/open-cart/view_cart_clicked, which a pinned card must
4355
+ // never do (Task 1 keeps the card pinned open across the add).
4356
+ state.optionsPinned) {
4721
4357
  return unchanged2(state);
4722
4358
  }
4723
4359
  const productId = state.product.id;
@@ -4785,7 +4421,7 @@ function serializeSelection(selection) {
4785
4421
  };
4786
4422
  }
4787
4423
  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") {
4424
+ 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
4425
  return unchanged2(state);
4790
4426
  }
4791
4427
  const variant = state.variants.payload.variants.find(
@@ -4844,6 +4480,9 @@ function transitionCard(state, event) {
4844
4480
  const commerce = event.available ? "available" : "unavailable";
4845
4481
  return commerce === state.commerce ? unchanged2(state) : { state: { ...state, commerce }, effects: [] };
4846
4482
  }
4483
+ if (event.type === "options-pinned") {
4484
+ return event.pinned === state.optionsPinned ? unchanged2(state) : { state: { ...state, optionsPinned: event.pinned }, effects: [] };
4485
+ }
4847
4486
  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
4487
  return unchanged2(state);
4849
4488
  }
@@ -4884,7 +4523,7 @@ function transitionCard(state, event) {
4884
4523
  return { state: resumed.state, effects: [failureEffect, ...resumed.effects] };
4885
4524
  }
4886
4525
  case "selection-changed":
4887
- if (state.commerce !== "available" || state.face !== "back" || state.variants.status !== "ready") {
4526
+ if (state.commerce !== "available" || !optionsOpen(state) || state.variants.status !== "ready") {
4888
4527
  return unchanged2(state);
4889
4528
  }
4890
4529
  return {
@@ -5109,9 +4748,9 @@ function createCardEffectExecutor(ports) {
5109
4748
  var VIEW_DETAIL_ACTION_ID = "view_detail";
5110
4749
  function pickDetailCta(ctas) {
5111
4750
  if (!ctas?.length) return void 0;
5112
- const navigates = ctas.filter((c2) => !!c2.href);
4751
+ const navigates = ctas.filter((c) => !!c.href);
5113
4752
  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];
4753
+ return navigates.find((c) => c.actionId === VIEW_DETAIL_ACTION_ID) ?? navigates.find((c) => c.variant && c.variant !== "primary") ?? navigates[0];
5115
4754
  }
5116
4755
 
5117
4756
  // src/sanitizer.ts
@@ -5157,12 +4796,12 @@ function isDangerousUrlAttr(name, value) {
5157
4796
  const normalized = normalizeUrlAttr(value);
5158
4797
  return normalized.startsWith("javascript:") || normalized.startsWith("vbscript:") || normalized.startsWith("data:");
5159
4798
  }
5160
- function sanitizeHtml(html31) {
4799
+ function sanitizeHtml(html32) {
5161
4800
  const hasNative = typeof window.Sanitizer === "function";
5162
4801
  if (hasNative) {
5163
4802
  try {
5164
- const s4 = new window.Sanitizer({});
5165
- const frag = s4.sanitizeToFragment(html31);
4803
+ const s = new window.Sanitizer({});
4804
+ const frag = s.sanitizeToFragment(html32);
5166
4805
  const div = document.createElement("div");
5167
4806
  div.append(frag);
5168
4807
  return div.innerHTML;
@@ -5170,7 +4809,7 @@ function sanitizeHtml(html31) {
5170
4809
  }
5171
4810
  }
5172
4811
  const tpl = document.createElement("template");
5173
- tpl.innerHTML = html31;
4812
+ tpl.innerHTML = html32;
5174
4813
  const root = tpl.content;
5175
4814
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
5176
4815
  const toRemove = [];
@@ -5197,24 +4836,71 @@ function sanitizeHtml(html31) {
5197
4836
  }
5198
4837
  return tpl.innerHTML;
5199
4838
  }
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);
4839
+ var DESCRIPTION_TAGS = /* @__PURE__ */ new Set([
4840
+ "h3",
4841
+ "h4",
4842
+ "h5",
4843
+ "h6",
4844
+ "p",
4845
+ "br",
4846
+ "ul",
4847
+ "ol",
4848
+ "li",
4849
+ "strong",
4850
+ "b",
4851
+ "em",
4852
+ "i",
4853
+ "u",
4854
+ "small",
4855
+ "sup",
4856
+ "sub",
4857
+ "table",
4858
+ "thead",
4859
+ "tbody",
4860
+ "tr",
4861
+ "th",
4862
+ "td",
4863
+ "blockquote"
4864
+ ]);
4865
+ var DEMOTED_HEADING = {
4866
+ h1: "h3",
4867
+ h2: "h3",
4868
+ h3: "h4",
4869
+ h4: "h5",
4870
+ h5: "h6",
4871
+ h6: "h6"
4872
+ };
4873
+ function sanitizeProductDescription(html32) {
4874
+ if (typeof html32 !== "string" || html32 === "") return "";
4875
+ const doc = new DOMParser().parseFromString(html32, "text/html");
4876
+ const out = document.createElement("div");
4877
+ const copy = (from, to) => {
4878
+ for (const child of Array.from(from.childNodes)) {
4879
+ if (child.nodeType === Node.TEXT_NODE) {
4880
+ to.appendChild(document.createTextNode(child.textContent ?? ""));
4881
+ continue;
4882
+ }
4883
+ if (!(child instanceof Element)) continue;
4884
+ const tag = child.tagName.toLowerCase();
4885
+ if (tag === "script" || tag === "style" || tag === "template" || tag === "iframe") continue;
4886
+ const kept = DEMOTED_HEADING[tag] ?? tag;
4887
+ if (DESCRIPTION_TAGS.has(kept)) {
4888
+ const el = document.createElement(kept);
4889
+ copy(child, el);
4890
+ to.appendChild(el);
4891
+ } else {
4892
+ copy(child, to);
4893
+ }
4894
+ }
4895
+ };
4896
+ copy(doc.body, out);
4897
+ return out.innerHTML;
5211
4898
  }
5212
4899
 
5213
4900
  // src/widgets/imageStandin.ts
5214
4901
  import { html as html7 } from "lit";
5215
- var IMAGE_STANDIN_BACKGROUND = "linear-gradient(135deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.02) 45%, rgba(0, 0, 0, 0.22)), var(--sc-color-primary, #3a3a3a)";
5216
4902
  function standinMonogram(name) {
5217
- const initial = ([...name.trim()][0] ?? "?").toUpperCase();
4903
+ const initial = standinInitial(name) ?? "?";
5218
4904
  return html7`<span
5219
4905
  data-standin-monogram
5220
4906
  style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font:700 2.4rem/1 var(--sc-font-family, inherit);color:var(--sc-launcher-color, #ffffff);opacity:0.85;letter-spacing:0.02em;user-select:none;"
@@ -5222,12 +4908,25 @@ function standinMonogram(name) {
5222
4908
  >`;
5223
4909
  }
5224
4910
 
4911
+ // src/widgets/presentationContext.ts
4912
+ function presentationFromContext(el) {
4913
+ let node = el;
4914
+ while (node) {
4915
+ if (node instanceof Element && node.getAttribute("data-product-presentation") === "double") {
4916
+ return "double";
4917
+ }
4918
+ const parent = node.parentNode;
4919
+ node = parent instanceof ShadowRoot ? parent.host : parent;
4920
+ }
4921
+ return void 0;
4922
+ }
4923
+
5225
4924
  // src/widgets/renderHealth.ts
5226
4925
  var BLANK_MIN_AREA = 60 * 60;
5227
4926
  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]}` : "";
4927
+ const seg = (e) => {
4928
+ const tag = e.tagName.toLowerCase();
4929
+ const cls = typeof e.className === "string" && e.className.trim() ? `.${e.className.trim().split(/\s+/)[0]}` : "";
5231
4930
  return `${tag}${cls}`;
5232
4931
  };
5233
4932
  const parts = [seg(el)];
@@ -5276,7 +4975,7 @@ function measureRenderHealth(card) {
5276
4975
  const r = el.getBoundingClientRect();
5277
4976
  if (r.width === 0 || r.height === 0) continue;
5278
4977
  const ownText = Array.from(el.childNodes).some(
5279
- (n2) => n2.nodeType === 3 && (n2.textContent ?? "").trim().length > 0
4978
+ (n) => n.nodeType === 3 && (n.textContent ?? "").trim().length > 0
5280
4979
  );
5281
4980
  if (ownText) hasText = true;
5282
4981
  if (el.tagName === "IMG") {
@@ -5310,8 +5009,8 @@ function measureRenderHealth(card) {
5310
5009
  Math.round(ar.top - r.top)
5311
5010
  );
5312
5011
  if (esc > 1 && !hasClipOkAncestor(el, card) && !hasClipOkAncestor(a, card)) {
5313
- const t2 = (el.textContent ?? "").trim();
5314
- if (t2 || ["IMG", "BUTTON", "A"].includes(el.tagName)) {
5012
+ const t = (el.textContent ?? "").trim();
5013
+ if (t || ["IMG", "BUTTON", "A"].includes(el.tagName)) {
5315
5014
  push({ path: elementPath(el, card), over_px: esc });
5316
5015
  }
5317
5016
  }
@@ -5345,9 +5044,9 @@ function reportRenderHealth(root, opts) {
5345
5044
  synEmit(`app:${opts.category}:blank`, base);
5346
5045
  return health;
5347
5046
  }
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");
5047
+ const paths = health.clips.map((c) => c.path).sort();
5048
+ const over_px = health.clips.reduce((m, c) => Math.max(m, c.over_px), 0);
5049
+ const identityTruncated = health.clips.some((c) => c.reason === "identity-truncated");
5351
5050
  synEmit(`app:${opts.category}:clipped`, {
5352
5051
  paths: paths.join(","),
5353
5052
  over_px,
@@ -5648,6 +5347,12 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5648
5347
  __privateAdd(this, _VariantPanelLit_instances);
5649
5348
  this.payload = null;
5650
5349
  this.productTitle = "";
5350
+ /** Set by a double-wide card presentation: the panel sits BESIDE the front
5351
+ * face rather than flipped to. There is no Back — the front already shows
5352
+ * the identity, so the panel's own title element is skipped too (the
5353
+ * "Showing options for …" live-region status line still speaks it, so
5354
+ * screen reader users get the same announcement either way). */
5355
+ this.pinned = false;
5651
5356
  /** Product-level hero image, passed from the card. Shown on the back face and
5652
5357
  * used as the fallback when the selected variant carries no featured_image. */
5653
5358
  this.productImage = null;
@@ -5671,8 +5376,8 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5671
5376
  this._expandedAxes = /* @__PURE__ */ new Set();
5672
5377
  /** @internal — Tier-C per-axis filter text. */
5673
5378
  this._axisFilter = {};
5674
- this._onKeydown = (e2) => {
5675
- if (e2.key === "Escape") {
5379
+ this._onKeydown = (e) => {
5380
+ if (e.key === "Escape") {
5676
5381
  this.dispatchEvent(new CustomEvent("variant-panel-back", { bubbles: true, composed: true }));
5677
5382
  }
5678
5383
  };
@@ -5794,7 +5499,7 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5794
5499
  />` : nothing3}
5795
5500
  </div>
5796
5501
  <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}
5502
+ ${this.productTitle && !this.pinned ? html8`<div class="svp-product-title" data-product-title data-critical-text>${this.productTitle}</div>` : nothing3}
5798
5503
  <div class="svp-attributes-scroll" data-attributes-scroll data-clip-ok>
5799
5504
  ${singleVariant ? html8`<div class="svp-single-variant-note" data-single-variant-note>
5800
5505
  Just one option — ready to add.
@@ -5813,7 +5518,7 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5813
5518
  // so Back fits beside it on the same row).
5814
5519
  ctaPrice != null ? html8`Add ${compareAt ? html8`<span class="svp-compare">$${compareAt.toFixed(2)}</span>` : nothing3}· $${ctaPrice.toFixed(2)}` : html8`Add to cart`}
5815
5520
  </button>
5816
- ${backBtn}
5521
+ ${this.pinned ? nothing3 : backBtn}
5817
5522
  </div>
5818
5523
  <div class="svp-live" data-live data-clip-ok aria-live="polite">
5819
5524
  ${this.productTitle ? `Showing options for ${this.productTitle}` : nothing3}
@@ -5907,8 +5612,8 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5907
5612
  )?.focus();
5908
5613
  });
5909
5614
  }
5910
- _onAxisFilterInput(attrKey, e2) {
5911
- this._axisFilter = { ...this._axisFilter, [attrKey]: e2.target.value };
5615
+ _onAxisFilterInput(attrKey, e) {
5616
+ this._axisFilter = { ...this._axisFilter, [attrKey]: e.target.value };
5912
5617
  }
5913
5618
  /** Density Tier C — select-style trigger + bounded scrollable listbox.
5914
5619
  * ONLY this axis collapses; siblings keep their own tier. Filter appears
@@ -5926,9 +5631,9 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5926
5631
  class="svp-collapse"
5927
5632
  data-collapsed-axis
5928
5633
  data-attribute-key=${attr.key}
5929
- @keydown=${(e2) => {
5930
- if (e2.key === "Escape" && expanded) {
5931
- e2.stopPropagation();
5634
+ @keydown=${(e) => {
5635
+ if (e.key === "Escape" && expanded) {
5636
+ e.stopPropagation();
5932
5637
  this._toggleAxis(attr.key);
5933
5638
  void this.updateComplete.then(() => {
5934
5639
  this.querySelector(
@@ -5965,7 +5670,7 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
5965
5670
  type="text"
5966
5671
  placeholder=${`Filter ${attr.label.toLowerCase()}\u2026`}
5967
5672
  .value=${this._axisFilter[attr.key] ?? ""}
5968
- @input=${(e2) => this._onAxisFilterInput(attr.key, e2)}
5673
+ @input=${(e) => this._onAxisFilterInput(attr.key, e)}
5969
5674
  />` : nothing3}
5970
5675
  ${filtered.map((v) => {
5971
5676
  const disabled2 = this._isValueDisabled(attr.key, v.id);
@@ -6007,6 +5712,7 @@ _VariantPanelLit.properties = {
6007
5712
  state: { type: String },
6008
5713
  error: { type: String },
6009
5714
  retryable: { type: Boolean },
5715
+ pinned: { type: Boolean },
6010
5716
  _selection: { state: true },
6011
5717
  _touched: { state: true },
6012
5718
  _expandedAxes: { state: true },
@@ -6127,7 +5833,7 @@ var CARD_FLIP_CSS = `
6127
5833
  .sc-product-card [data-card-flip] { animation: none; }
6128
5834
  }
6129
5835
  `;
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;
5836
+ 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
5837
  var ProductCardLit = class extends LitElement8 {
6132
5838
  constructor() {
6133
5839
  super(...arguments);
@@ -6151,6 +5857,12 @@ var ProductCardLit = class extends LitElement8 {
6151
5857
  }));
6152
5858
  __privateAdd(this, _controller);
6153
5859
  __privateAdd(this, _unsubDevice);
5860
+ /** Set once at connect from the canvas DOM around the card
5861
+ * (`data-product-presentation="double"`) — never from `props`, so the
5862
+ * agent that writes card props has no path to setting it. Drives the
5863
+ * double-wide render branch: front face and options side by side, options
5864
+ * pinned open. */
5865
+ __privateAdd(this, _double, false);
6154
5866
  /** Fingerprint of the last reported schema-validation failure (sorted
6155
5867
  * invalid paths). Guards `render_failed` telemetry against re-render spam:
6156
5868
  * the same bad props re-rendering must not re-emit. Reset on a successful
@@ -6164,25 +5876,25 @@ var ProductCardLit = class extends LitElement8 {
6164
5876
  * the next scheduled pass or on disconnect. */
6165
5877
  __privateAdd(this, _healthRaf);
6166
5878
  /** 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" });
5879
+ __privateAdd(this, _onFlipAnimationEnd, (e) => {
5880
+ if (e.animationName === "sc-card-flip-in") __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "animation-settled" });
6169
5881
  });
6170
5882
  this._onVariantBack = () => {
6171
5883
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6172
5884
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "back-activated" });
6173
5885
  };
6174
- this._onVariantSelectionChange = (e2) => {
5886
+ this._onVariantSelectionChange = (e) => {
6175
5887
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6176
- const detail = e2.detail;
5888
+ const detail = e.detail;
6177
5889
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, {
6178
5890
  type: "selection-changed",
6179
5891
  attributeKey: detail.attribute_key,
6180
5892
  valueId: detail.value_id
6181
5893
  });
6182
5894
  };
6183
- this._onVariantAdd = (e2) => {
5895
+ this._onVariantAdd = (e) => {
6184
5896
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6185
- const detail = e2.detail;
5897
+ const detail = e.detail;
6186
5898
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "variant-add-activated", variantId: detail.variant.id });
6187
5899
  };
6188
5900
  this._onVariantRetry = () => {
@@ -6195,6 +5907,8 @@ var ProductCardLit = class extends LitElement8 {
6195
5907
  }
6196
5908
  connectedCallback() {
6197
5909
  super.connectedCallback();
5910
+ __privateSet(this, _double, presentationFromContext(this) === "double");
5911
+ if (__privateGet(this, _double)) __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "options-pinned", pinned: true });
6198
5912
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "reconnected" });
6199
5913
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, {
6200
5914
  type: "commerce-updated",
@@ -6204,8 +5918,8 @@ var ProductCardLit = class extends LitElement8 {
6204
5918
  this.style.color = "var(--sc-tile-text-color, rgba(20, 20, 20, 0.92))";
6205
5919
  this._device = _readDevice();
6206
5920
  const ctx = synWindow().SynOS?.runtime?.context;
6207
- __privateSet(this, _unsubDevice, ctx?.subscribe?.((c2) => {
6208
- const d = c2?.surface?.device;
5921
+ __privateSet(this, _unsubDevice, ctx?.subscribe?.((c) => {
5922
+ const d = c?.surface?.device;
6209
5923
  const next = d === "mobile" || d === "tablet" ? d : "desktop";
6210
5924
  if (next !== this._device) this._device = next;
6211
5925
  }));
@@ -6238,18 +5952,37 @@ var ProductCardLit = class extends LitElement8 {
6238
5952
  const resolved = this.resolved[product.id];
6239
5953
  const view = selectCardView(this._decisionState);
6240
5954
  const articleStyles = _buildArticleStyles(density, view.face);
6241
- const onPrimaryCtaClick = (e2) => {
6242
- e2.preventDefault();
5955
+ const onPrimaryCtaClick = (e) => {
5956
+ e.preventDefault();
6243
5957
  __privateMethod(this, _ProductCardLit_instances, syncDecisionInputsBeforeIntent_fn).call(this);
6244
5958
  const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
6245
5959
  __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "primary-activated", reducedMotion });
6246
5960
  };
6247
- const ctasWithMorphedLabel = product.ctas.map((c2, i2) => {
6248
- if (i2 === 0 && view.primaryCta.mode === "view-cart") {
6249
- return { ...c2, label: "View cart \u2192" };
5961
+ const ctasWithMorphedLabel = product.ctas.map((c, i) => {
5962
+ if (i === 0 && view.primaryCta.mode === "view-cart") {
5963
+ return { ...c, label: "View cart \u2192" };
6250
5964
  }
6251
- return c2;
5965
+ return c;
6252
5966
  });
5967
+ if (__privateGet(this, _double)) {
5968
+ return html9`<article
5969
+ class="sc-product-card"
5970
+ data-presentation="double"
5971
+ data-density=${density}
5972
+ style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr));gap:16px;align-items:start;"
5973
+ >
5974
+ <div data-face="front">
5975
+ ${renderProductCardInner(product, density, resolved, visibleFacts, [], void 0, true)}
5976
+ </div>
5977
+ <div data-face="back">
5978
+ ${__privateMethod(this, _ProductCardLit_instances, renderVariantPanel_fn).call(this, view, product, {
5979
+ pinned: true,
5980
+ productImage: null,
5981
+ wireBack: false
5982
+ })}
5983
+ </div>
5984
+ </article>`;
5985
+ }
6253
5986
  const frontContent = renderProductCardInner(
6254
5987
  product,
6255
5988
  density,
@@ -6259,19 +5992,11 @@ var ProductCardLit = class extends LitElement8 {
6259
5992
  onPrimaryCtaClick,
6260
5993
  !view.primaryCta.disabled
6261
5994
  );
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}`;
5995
+ const backFace = __privateMethod(this, _ProductCardLit_instances, renderVariantPanel_fn).call(this, view, product, {
5996
+ pinned: false,
5997
+ productImage: product.image ? { src: product.image.src, alt: product.image.alt } : null,
5998
+ wireBack: true
5999
+ });
6275
6000
  const backView = view.face === "back" && view.flipAnimating ? (
6276
6001
  // Mid-flip: both faces coexist inside a preserve-3d container. The
6277
6002
  // front turns away (inert + aria-hidden) while the back turns in.
@@ -6302,6 +6027,7 @@ var ProductCardLit = class extends LitElement8 {
6302
6027
  _effectExecutor = new WeakMap();
6303
6028
  _controller = new WeakMap();
6304
6029
  _unsubDevice = new WeakMap();
6030
+ _double = new WeakMap();
6305
6031
  _lastFailureFingerprint = new WeakMap();
6306
6032
  _reportedHealthStates = new WeakMap();
6307
6033
  _healthRaf = new WeakMap();
@@ -6366,6 +6092,7 @@ dispatchCard_fn = function(event) {
6366
6092
  _onFlipAnimationEnd = new WeakMap();
6367
6093
  startBind_fn = function() {
6368
6094
  __privateGet(this, _controller)?.destroy();
6095
+ const previous = this.resolved;
6369
6096
  this.resolved = {};
6370
6097
  if (!this.props) {
6371
6098
  this.validationError = false;
@@ -6392,15 +6119,41 @@ startBind_fn = function() {
6392
6119
  __privateSet(this, _lastFailureFingerprint, null);
6393
6120
  this.validationError = false;
6394
6121
  const parsed = result.data;
6122
+ this.resolved = keepResolved(previous, [parsed.product]);
6395
6123
  __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
- };
6124
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
6401
6125
  }));
6402
6126
  __privateGet(this, _controller).start();
6403
6127
  };
6128
+ /**
6129
+ * The `<syntro-variant-panel>` markup, shared by the flip card's back face
6130
+ * and the double-wide presentation's side panel — everything (payload,
6131
+ * title, state, error, retryable, the add/retry/selection-change handlers,
6132
+ * and the addError block) is identical between the two callers. Only three
6133
+ * things differ, so they're the only knobs: `pinned` (double-wide has no
6134
+ * Back and no title element — see VariantPanelLit), `productImage` (the
6135
+ * flip back face shows the product's hero image; the double-wide panel
6136
+ * shows none, since the front face right beside it already carries the
6137
+ * image), and `wireBack` (the double-wide panel has no Back control to
6138
+ * fire it and the card never flips there, so wiring the handler would be
6139
+ * live but pointless — omitted to match the panel's own no-Back contract).
6140
+ */
6141
+ renderVariantPanel_fn = function(view, product, options) {
6142
+ return html9`<syntro-variant-panel
6143
+ ?pinned=${options.pinned}
6144
+ .payload=${view.panel.payload}
6145
+ .productTitle=${product.name ?? ""}
6146
+ .productImage=${options.productImage}
6147
+ .state=${view.panel.status === "error" ? "error" : view.panel.status === "ready" ? "idle" : "loading"}
6148
+ .error=${view.panel.error}
6149
+ .retryable=${view.panel.retryable}
6150
+ @variant-panel-back=${options.wireBack ? this._onVariantBack : void 0}
6151
+ @variant-panel-add=${this._onVariantAdd}
6152
+ @variant-panel-retry=${this._onVariantRetry}
6153
+ @variant-panel-selection-change=${this._onVariantSelectionChange}
6154
+ ></syntro-variant-panel>
6155
+ ${view.addError ? html9`<div data-add-to-cart-error>${view.addError}</div>` : nothing4}`;
6156
+ };
6404
6157
  readProductObservation_fn = function() {
6405
6158
  const parsed = this.props ? cardSchema.safeParse(this.props) : null;
6406
6159
  const rawProductId = this.props?.product?.id;
@@ -6527,7 +6280,9 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6527
6280
  const factOrder = Array.from(
6528
6281
  /* @__PURE__ */ new Set([...CORE_FACTS, ...visibleFacts ?? VISIBLE_FACT_KEYS])
6529
6282
  );
6530
- const hasSalePrice = product.salePrice !== void 0;
6283
+ const storeAnswered = resolved !== void 0 && "compareAt" in resolved;
6284
+ const hasSalePrice = storeAnswered ? Boolean(resolved?.compareAt) : product.salePrice !== void 0;
6285
+ const originalPriceText = storeAnswered ? resolved?.compareAt : product.price?.amount;
6531
6286
  const activePriceText = resolved?.price ?? product.salePrice?.amount ?? product.price?.amount;
6532
6287
  const activeCadence = hasSalePrice ? product.salePrice?.cadence : product.price?.cadence;
6533
6288
  const overlay = density === "compact";
@@ -6830,10 +6585,15 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6830
6585
  borderRadius: "9999px",
6831
6586
  border: "0",
6832
6587
  background: "transparent",
6833
- color: "var(--sc-color-primary, #3d8a5e)",
6588
+ // Accent as ink on `transparent` is ink on the HOST's ground inside a
6589
+ // card, where vela's #723b32 measures 1.3:1. The host's own ink wins
6590
+ // there; the merchant's accent everywhere else.
6591
+ color: "var(--sc-host-link-color, var(--sc-color-primary, #3d8a5e))",
6834
6592
  fontWeight: "600",
6835
6593
  fontSize: "0.82rem",
6836
- textDecoration: "none",
6594
+ // Once the link is the same colour as the text around it, colour alone no
6595
+ // longer says it is a link (WCAG 1.4.1), so the host card underlines it.
6596
+ textDecoration: "var(--sc-host-link-decoration, none)",
6837
6597
  textAlign: "center",
6838
6598
  lineHeight: "1.15",
6839
6599
  // Same terminal policy as the primary pill: shrink + wrap, never overflow.
@@ -6843,41 +6603,41 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
6843
6603
  maxWidth: "100%",
6844
6604
  flexShrink: "1"
6845
6605
  };
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;
6606
+ const ctaRow = ctas.length === 0 ? nothing4 : html9`<div class="sc-product-card__ctas" style=${styleMap(ctasStyles)}>
6607
+ ${ctas.map((c, i) => {
6608
+ const disabled2 = c.actionId === "add_to_cart" && !commerceAvailable;
6849
6609
  const ctaStyles = {
6850
- ...i2 === 0 ? primaryCtaStyles : secondaryCtaStyles,
6610
+ ...i === 0 ? primaryCtaStyles : secondaryCtaStyles,
6851
6611
  ...disabled2 ? { opacity: "0.5", "pointer-events": "none", cursor: "not-allowed" } : {}
6852
6612
  };
6853
- const ctaLabel = c2.label;
6613
+ const ctaLabel = c.label;
6854
6614
  return html9`<a
6855
6615
  class="sc-product-card__cta"
6856
- data-product-cta=${i2 === 0 ? "primary" : "secondary"}
6616
+ data-product-cta=${i === 0 ? "primary" : "secondary"}
6857
6617
  data-critical-text
6858
- data-variant=${c2.variant}
6618
+ data-variant=${c.variant}
6859
6619
  data-disabled=${disabled2 ? "true" : nothing4}
6860
6620
  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 ?? ""}
6621
+ href=${c.actionId === "add_to_cart" ? "#" : c.href ?? "#"}
6622
+ target=${c.target}
6623
+ rel=${c.target === "_blank" ? "noopener noreferrer" : ""}
6624
+ data-action-id=${c.actionId ?? ""}
6865
6625
  style=${styleMap(ctaStyles)}
6866
- @click=${disabled2 ? (e2) => {
6867
- e2.preventDefault();
6626
+ @click=${disabled2 ? (e) => {
6627
+ e.preventDefault();
6868
6628
  } : (
6869
6629
  // The commerce (Add-to-cart / open-picker) intent wires ONLY to
6870
6630
  // an actual add_to_cart CTA. A plain navigating CTA — e.g. a
6871
6631
  // booking-fulfilment product's "Book a consult" href, which
6872
6632
  // sits at i===0 too — must fall through to its own <a href>
6873
6633
  // 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();
6634
+ i === 0 && c.actionId === "add_to_cart" && onPrimaryCtaClick ? onPrimaryCtaClick : c.actionId === "add_to_cart" ? (e) => {
6635
+ e.preventDefault();
6876
6636
  } : void 0
6877
6637
  )}
6878
6638
  >${// Secondary "Product Page" link gets a trailing chevron (panel's
6879
6639
  // quiet-secondary affordance) unless the author already added one.
6880
- i2 !== 0 && !/[›>→]\s*$/.test(ctaLabel) ? `${ctaLabel} \u203A` : ctaLabel}</a
6640
+ i !== 0 && !/[›>→]\s*$/.test(ctaLabel) ? `${ctaLabel} \u203A` : ctaLabel}</a
6881
6641
  >`;
6882
6642
  })}
6883
6643
  </div>`;
@@ -7080,11 +6840,11 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
7080
6840
  data-product-price
7081
6841
  style=${styleMap(priceBlockStyles)}
7082
6842
  >
7083
- ${hasSalePrice && product.price ? html9`<span
6843
+ ${hasSalePrice && originalPriceText ? html9`<span
7084
6844
  class="sc-product-card__amount-original"
7085
6845
  data-product-price-original
7086
6846
  style=${styleMap(priceOriginalStyles)}
7087
- >${product.price.amount}</span
6847
+ >${originalPriceText}</span
7088
6848
  >` : nothing4}
7089
6849
  <span
7090
6850
  class="sc-product-card__amount"
@@ -7246,8 +7006,8 @@ function renderProductCard(product, density, resolved, visibleFacts) {
7246
7006
  </article>
7247
7007
  `;
7248
7008
  }
7249
- function onImageError(e2) {
7250
- const img = e2.currentTarget;
7009
+ function onImageError(e) {
7010
+ const img = e.currentTarget;
7251
7011
  img.dataset.imageError = "true";
7252
7012
  img.removeAttribute("src");
7253
7013
  }
@@ -7422,6 +7182,7 @@ syncScrollCue_fn = function() {
7422
7182
  };
7423
7183
  startBind_fn2 = function() {
7424
7184
  __privateGet(this, _controller2)?.destroy();
7185
+ const previous = this.resolved;
7425
7186
  this.resolved = {};
7426
7187
  if (!this.props) {
7427
7188
  this.validationError = false;
@@ -7437,12 +7198,9 @@ startBind_fn2 = function() {
7437
7198
  }
7438
7199
  this.validationError = false;
7439
7200
  __privateSet(this, _parsed, result.data);
7201
+ this.resolved = keepResolved(previous, result.data.products);
7440
7202
  __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
- };
7203
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
7446
7204
  }));
7447
7205
  __privateGet(this, _controller2).start();
7448
7206
  };
@@ -7457,7 +7215,7 @@ startBind_fn2 = function() {
7457
7215
  */
7458
7216
  renderDiveDeeper_fn = function(parsed) {
7459
7217
  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;";
7218
+ 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
7219
  return html10`<button
7462
7220
  type="button"
7463
7221
  class="sc-product-comparison__dive-deeper"
@@ -7502,8 +7260,8 @@ rowComplete_fn = function(row, productCount) {
7502
7260
  maxFields_fn = function() {
7503
7261
  const raw = getComputedStyle(this).getPropertyValue("--sc-comparison-max-fields").trim();
7504
7262
  if (!raw) return null;
7505
- const n2 = Number.parseInt(raw, 10);
7506
- return Number.isFinite(n2) && n2 > 0 ? n2 : null;
7263
+ const n = Number.parseInt(raw, 10);
7264
+ return Number.isFinite(n) && n > 0 ? n : null;
7507
7265
  };
7508
7266
  /**
7509
7267
  * Build comparison rows from the products' own `attributes` when the assistant
@@ -7520,10 +7278,10 @@ synthesizeRows_fn = function(products) {
7520
7278
  }
7521
7279
  }
7522
7280
  return labels.map((label) => {
7523
- const [a, b, c2, d] = products.map(
7281
+ const [a, b, c, d] = products.map(
7524
7282
  (p) => (p.attributes ?? []).find((x) => x.label === label)?.value ?? "\u2014"
7525
7283
  );
7526
- return { label, valueA: a ?? "\u2014", valueB: b ?? "\u2014", valueC: c2, valueD: d };
7284
+ return { label, valueA: a ?? "\u2014", valueB: b ?? "\u2014", valueC: c, valueD: d };
7527
7285
  });
7528
7286
  };
7529
7287
  /**
@@ -7535,8 +7293,8 @@ renderMatrix_fn = function(products, rows) {
7535
7293
  const cols = `minmax(62px, 0.55fr) repeat(${products.length}, minmax(0, 1fr))`;
7536
7294
  const compactHeaders = products.length >= 3;
7537
7295
  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;";
7296
+ 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;";
7297
+ 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
7298
  return html10`
7541
7299
  <div
7542
7300
  class="sc-product-comparison__matrix"
@@ -7638,8 +7396,8 @@ renderMatrix_fn = function(products, rows) {
7638
7396
  >${row.label}</span
7639
7397
  >
7640
7398
  ${products.map(
7641
- (_p, i2) => html10`<span class="sc-product-comparison__matrix-cell" role="cell" style=${cellStyle}
7642
- >${cells[i2] ?? "\u2014"}</span
7399
+ (_p, i) => html10`<span class="sc-product-comparison__matrix-cell" role="cell" style=${cellStyle}
7400
+ >${cells[i] ?? "\u2014"}</span
7643
7401
  >`
7644
7402
  )}
7645
7403
  </div>
@@ -7655,10 +7413,167 @@ ProductComparisonLit.properties = {
7655
7413
  scrollFade: { state: true }
7656
7414
  };
7657
7415
 
7658
- // src/widgets/ProductGridLit.ts
7416
+ // src/widgets/ProductDetailsLit.ts
7659
7417
  import { html as html11, LitElement as LitElement10, nothing as nothing6 } from "lit";
7418
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
7419
+ var EMPTY_MARKER = html11`<span data-sc-empty hidden></span>`;
7420
+ var DETAILS_CSS = `
7421
+ .pd {
7422
+ max-width: 1040px;
7423
+ margin: 0 auto 16px;
7424
+ display: grid;
7425
+ gap: 16px;
7426
+ }
7427
+ .pd-lead p {
7428
+ margin: 0 0 6px;
7429
+ }
7430
+ .pd-eyebrow {
7431
+ font-size: 0.75rem;
7432
+ letter-spacing: 0.06em;
7433
+ text-transform: uppercase;
7434
+ opacity: 0.75;
7435
+ }
7436
+ .pd-badge {
7437
+ margin-left: 6px;
7438
+ padding: 1px 8px;
7439
+ border-radius: 9999px;
7440
+ box-shadow: inset 0 0 0 1px currentColor;
7441
+ text-transform: none;
7442
+ letter-spacing: 0;
7443
+ }
7444
+ .pd-rating {
7445
+ font-size: 0.875rem;
7446
+ }
7447
+ .pd-desc {
7448
+ line-height: 1.5;
7449
+ max-width: 70ch;
7450
+ }
7451
+ .pd-benefits {
7452
+ margin: 4px 0 0;
7453
+ padding-left: 18px;
7454
+ }
7455
+ .pd-cols {
7456
+ display: grid;
7457
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
7458
+ gap: 16px;
7459
+ padding: 14px 0;
7460
+ border-top: 1px solid color-mix(in srgb, currentColor 18%, transparent);
7461
+ border-bottom: 1px solid color-mix(in srgb, currentColor 18%, transparent);
7462
+ font-size: 0.875rem;
7463
+ }
7464
+ .pd-cols h3 {
7465
+ margin: 0 0 6px;
7466
+ font-size: 0.8125rem;
7467
+ letter-spacing: 0.04em;
7468
+ text-transform: uppercase;
7469
+ opacity: 0.8;
7470
+ }
7471
+ .pd-cols p,
7472
+ .pd-cols ul {
7473
+ margin: 0 0 4px;
7474
+ }
7475
+ .pd-cols ul {
7476
+ padding-left: 18px;
7477
+ }
7478
+ `;
7479
+ var ProductDetailsLit = class extends LitElement10 {
7480
+ constructor() {
7481
+ super(...arguments);
7482
+ this._productId = "";
7483
+ this._details = null;
7484
+ }
7485
+ createRenderRoot() {
7486
+ return this;
7487
+ }
7488
+ connectedCallback() {
7489
+ super.connectedCallback();
7490
+ ensureStylesInRoot(this, "syntro-product-details-style", DETAILS_CSS);
7491
+ }
7492
+ /**
7493
+ * Widget props (the `adaptive-product:details` mountable sets `el.props`
7494
+ * before appending). Starts the details fetch immediately — reading
7495
+ * `productId` off the adapter, not off any attribute — and keys the
7496
+ * in-flight request by productId so a stale answer arriving after a later
7497
+ * `props` set (e.g. the card swapped to a different product) is dropped.
7498
+ */
7499
+ set props(value) {
7500
+ const id = value?.productId ?? "";
7501
+ this._productId = id;
7502
+ this._details = null;
7503
+ if (!id) return;
7504
+ const adapter = getProductPlatformAdapter();
7505
+ if (typeof adapter?.fetchProductDetails !== "function") return;
7506
+ adapter.fetchProductDetails(id).then((details) => {
7507
+ if (this._productId !== id) return;
7508
+ this._details = details;
7509
+ }).catch(() => {
7510
+ });
7511
+ }
7512
+ get props() {
7513
+ return this._productId ? { productId: this._productId } : void 0;
7514
+ }
7515
+ render() {
7516
+ const details = this._details;
7517
+ if (!details) return EMPTY_MARKER;
7518
+ const d = details.details;
7519
+ const eyebrow = [d?.category, d?.gender].filter(Boolean).join(" \xB7 ");
7520
+ const badges = d?.badges ?? [];
7521
+ const benefits = d?.benefits ?? [];
7522
+ const moreBenefits = benefits.slice(3);
7523
+ const hasFabric = Boolean(d?.fabric || moreBenefits.length);
7524
+ const hasCols = Boolean(
7525
+ d && (hasFabric || (d.care?.length ?? 0) > 0 || d.shipping || d.returns)
7526
+ );
7527
+ const hasLead = Boolean(
7528
+ eyebrow || badges.length || details.rating || details.description || benefits.length
7529
+ );
7530
+ if (!hasLead && !hasCols) return EMPTY_MARKER;
7531
+ return html11`
7532
+ <section class="pd" data-product-details aria-label="Product details">
7533
+ <div class="pd-lead">
7534
+ ${eyebrow || badges.length ? html11`<p class="pd-eyebrow">
7535
+ ${eyebrow}${badges.map((b) => html11` <span class="pd-badge">${b}</span>`)}
7536
+ </p>` : nothing6}
7537
+ ${details.rating ? html11`<p class="pd-rating">
7538
+ ★ ${details.rating.value} · ${details.rating.count.toLocaleString("en-US")} reviews
7539
+ </p>` : nothing6}
7540
+ ${details.description ? html11`<div class="pd-desc">${unsafeHTML(sanitizeProductDescription(details.description))}</div>` : nothing6}
7541
+ ${benefits.length ? html11`<ul class="pd-benefits">
7542
+ ${benefits.slice(0, 3).map((b) => html11`<li>${b}</li>`)}
7543
+ </ul>` : nothing6}
7544
+ </div>
7545
+ ${hasCols ? html11`<div class="pd-cols">
7546
+ ${hasFabric ? html11`<div>
7547
+ <h3>Fabric &amp; benefits</h3>
7548
+ ${d?.fabric ? html11`<p>${d.fabric}</p>` : nothing6}
7549
+ ${moreBenefits.length ? html11`<ul>
7550
+ ${moreBenefits.map((b) => html11`<li>${b}</li>`)}
7551
+ </ul>` : nothing6}
7552
+ </div>` : nothing6}
7553
+ <div>
7554
+ <h3>Shipping &amp; returns</h3>
7555
+ ${d?.shipping ? html11`<p>${d.shipping}</p>` : nothing6}
7556
+ ${d?.returns ? html11`<p>${d.returns}</p>` : nothing6}
7557
+ </div>
7558
+ <div>
7559
+ <h3>Care</h3>
7560
+ ${d?.care?.length ? html11`<ul>
7561
+ ${d.care.map((c) => html11`<li>${c}</li>`)}
7562
+ </ul>` : nothing6}
7563
+ </div>
7564
+ </div>` : nothing6}
7565
+ </section>
7566
+ `;
7567
+ }
7568
+ };
7569
+ ProductDetailsLit.properties = {
7570
+ _details: { state: true }
7571
+ };
7572
+
7573
+ // src/widgets/ProductGridLit.ts
7574
+ import { html as html12, LitElement as LitElement11, nothing as nothing7 } from "lit";
7660
7575
  var _controller3, _ProductGridLit_instances, startBind_fn3;
7661
- var ProductGridLit = class extends LitElement10 {
7576
+ var ProductGridLit = class extends LitElement11 {
7662
7577
  constructor() {
7663
7578
  super(...arguments);
7664
7579
  __privateAdd(this, _ProductGridLit_instances);
@@ -7684,20 +7599,20 @@ var ProductGridLit = class extends LitElement10 {
7684
7599
  __privateGet(this, _controller3)?.destroy();
7685
7600
  }
7686
7601
  render() {
7687
- if (this.validationError || !this.props) return html11``;
7602
+ if (this.validationError || !this.props) return html12``;
7688
7603
  const parsed = gridSchema.safeParse(this.props);
7689
- if (!parsed.success) return html11``;
7604
+ if (!parsed.success) return html12``;
7690
7605
  const { products, desktopColumns, heading } = parsed.data;
7691
- return html11`
7606
+ return html12`
7692
7607
  <section class="sc-product-grid">
7693
- ${heading ? html11`<h2 data-grid-heading class="sc-product-grid__heading">${heading}</h2>` : nothing6}
7608
+ ${heading ? html12`<h2 data-grid-heading class="sc-product-grid__heading">${heading}</h2>` : nothing7}
7694
7609
  <div
7695
7610
  data-grid-root
7696
7611
  class="sc-product-grid__cells"
7697
7612
  style=${`--sc-product-grid-cols:${desktopColumns}`}
7698
7613
  >
7699
7614
  ${products.map(
7700
- (p) => html11`<div data-grid-cell class="sc-product-grid__cell">
7615
+ (p) => html12`<div data-grid-cell class="sc-product-grid__cell">
7701
7616
  ${renderProductCard(p, "standard", this.resolved[p.id])}
7702
7617
  </div>`
7703
7618
  )}
@@ -7710,6 +7625,7 @@ _controller3 = new WeakMap();
7710
7625
  _ProductGridLit_instances = new WeakSet();
7711
7626
  startBind_fn3 = function() {
7712
7627
  __privateGet(this, _controller3)?.destroy();
7628
+ const previous = this.resolved;
7713
7629
  this.resolved = {};
7714
7630
  if (!this.props) {
7715
7631
  this.validationError = false;
@@ -7722,12 +7638,9 @@ startBind_fn3 = function() {
7722
7638
  return;
7723
7639
  }
7724
7640
  this.validationError = false;
7641
+ this.resolved = keepResolved(previous, result.data.products);
7725
7642
  __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
- };
7643
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
7731
7644
  }));
7732
7645
  __privateGet(this, _controller3).start();
7733
7646
  };
@@ -7738,9 +7651,9 @@ ProductGridLit.properties = {
7738
7651
  };
7739
7652
 
7740
7653
  // src/widgets/ProductHeroLit.ts
7741
- import { html as html12, LitElement as LitElement11, nothing as nothing7 } from "lit";
7654
+ import { html as html13, LitElement as LitElement12, nothing as nothing8 } from "lit";
7742
7655
  var _controller4, _ProductHeroLit_instances, startBind_fn4;
7743
- var ProductHeroLit = class extends LitElement11 {
7656
+ var ProductHeroLit = class extends LitElement12 {
7744
7657
  constructor() {
7745
7658
  super(...arguments);
7746
7659
  __privateAdd(this, _ProductHeroLit_instances);
@@ -7766,9 +7679,9 @@ var ProductHeroLit = class extends LitElement11 {
7766
7679
  __privateGet(this, _controller4)?.destroy();
7767
7680
  }
7768
7681
  render() {
7769
- if (this.validationError || !this.props) return html12``;
7682
+ if (this.validationError || !this.props) return html13``;
7770
7683
  const parsed = heroSchema.safeParse(this.props);
7771
- if (!parsed.success) return html12``;
7684
+ if (!parsed.success) return html13``;
7772
7685
  const { product, layout, longCopy } = parsed.data;
7773
7686
  const resolved = this.resolved[product.id];
7774
7687
  const imgSrc = resolved?.image ?? product.image?.src;
@@ -7777,15 +7690,15 @@ var ProductHeroLit = class extends LitElement11 {
7777
7690
  const sectionStyles = isTopLayout ? "display:block;" : `display:flex;flex-direction:${layout === "image-right" ? "row-reverse" : "row"};gap:1rem;align-items:flex-start;`;
7778
7691
  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
7692
  const contentStyles = isTopLayout ? "" : "flex:1;min-width:0;";
7780
- return html12`
7693
+ return html13`
7781
7694
  <section class="sc-product-hero" data-hero-layout=${layout} style=${sectionStyles}>
7782
- ${imgSrc ? html12`<img
7695
+ ${imgSrc ? html13`<img
7783
7696
  class="sc-product-hero__image"
7784
7697
  src=${imgSrc}
7785
7698
  alt=${product.image?.alt ?? product.name}
7786
7699
  loading="lazy"
7787
7700
  style=${imageStyles}
7788
- />` : html12`<div
7701
+ />` : html13`<div
7789
7702
  class="sc-product-hero__image"
7790
7703
  data-product-image-standin
7791
7704
  aria-hidden="true"
@@ -7793,25 +7706,25 @@ var ProductHeroLit = class extends LitElement11 {
7793
7706
  >${standinMonogram(product.name)}</div>`}
7794
7707
  <div class="sc-product-hero__content" style=${contentStyles}>
7795
7708
  <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
7709
+ ${product.tagline ? html13`<p class="sc-product-hero__tagline">${product.tagline}</p>` : nothing8}
7710
+ ${product.price ? html13`<p class="sc-product-hero__price">
7711
+ ${priceText}${product.price.cadence ? html13` <small>${product.price.cadence}</small>` : nothing8}
7712
+ </p>` : nothing8}
7713
+ ${longCopy ? html13`<div
7801
7714
  data-hero-longcopy
7802
7715
  class="sc-product-hero__longcopy"
7803
7716
  .innerHTML=${sanitizeHtml(longCopy)}
7804
- ></div>` : nothing7}
7717
+ ></div>` : nothing8}
7805
7718
  <div class="sc-product-hero__ctas">
7806
7719
  ${product.ctas.map(
7807
- (c2, i2) => html12`<a
7720
+ (c, i) => html13`<a
7808
7721
  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
7722
+ data-product-cta=${i === 0 ? "primary" : "secondary"}
7723
+ data-variant=${c.variant}
7724
+ href=${c.href ?? "#"}
7725
+ target=${c.target}
7726
+ rel=${c.target === "_blank" ? "noopener noreferrer" : ""}
7727
+ >${c.label}</a
7815
7728
  >`
7816
7729
  )}
7817
7730
  </div>
@@ -7824,6 +7737,7 @@ _controller4 = new WeakMap();
7824
7737
  _ProductHeroLit_instances = new WeakSet();
7825
7738
  startBind_fn4 = function() {
7826
7739
  __privateGet(this, _controller4)?.destroy();
7740
+ const previous = this.resolved;
7827
7741
  this.resolved = {};
7828
7742
  if (!this.props) {
7829
7743
  this.validationError = false;
@@ -7836,12 +7750,9 @@ startBind_fn4 = function() {
7836
7750
  return;
7837
7751
  }
7838
7752
  this.validationError = false;
7753
+ this.resolved = keepResolved(previous, [result.data.product]);
7839
7754
  __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
- };
7755
+ this.resolved = mergeResolved(this.resolved, productId, kind, value);
7845
7756
  }));
7846
7757
  __privateGet(this, _controller4).start();
7847
7758
  };
@@ -7852,15 +7763,17 @@ ProductHeroLit.properties = {
7852
7763
  };
7853
7764
 
7854
7765
  // src/widgets/ProductRecoCarouselLit.ts
7855
- import { html as html13, LitElement as LitElement12, nothing as nothing8 } from "lit";
7766
+ import { html as html14, LitElement as LitElement13, nothing as nothing9 } from "lit";
7856
7767
  import { styleMap as styleMap2 } from "lit/directives/style-map.js";
7857
- var _ProductRecoCarouselLit_instances, healthProbe_fn3;
7858
- var ProductRecoCarouselLit = class extends LitElement12 {
7768
+ var _unwatch3, _ProductRecoCarouselLit_instances, healthProbe_fn3, watchPrices_fn, stopWatching_fn;
7769
+ var ProductRecoCarouselLit = class extends LitElement13 {
7859
7770
  constructor() {
7860
7771
  super();
7861
7772
  __privateAdd(this, _ProductRecoCarouselLit_instances);
7773
+ __privateAdd(this, _unwatch3, []);
7862
7774
  this.props = void 0;
7863
7775
  this._parsed = void 0;
7776
+ this._live = {};
7864
7777
  new RenderHealthController(this, () => __privateMethod(this, _ProductRecoCarouselLit_instances, healthProbe_fn3).call(this));
7865
7778
  }
7866
7779
  createRenderRoot() {
@@ -7870,32 +7783,54 @@ var ProductRecoCarouselLit = class extends LitElement12 {
7870
7783
  if (changed.has("props")) {
7871
7784
  const r = this.props ? recoSchema.safeParse(this.props) : void 0;
7872
7785
  this._parsed = r?.success ? r.data : void 0;
7786
+ this._live = {};
7787
+ if (this.isConnected) __privateMethod(this, _ProductRecoCarouselLit_instances, watchPrices_fn).call(this);
7873
7788
  }
7874
7789
  }
7790
+ connectedCallback() {
7791
+ super.connectedCallback();
7792
+ __privateMethod(this, _ProductRecoCarouselLit_instances, watchPrices_fn).call(this);
7793
+ }
7794
+ disconnectedCallback() {
7795
+ super.disconnectedCallback();
7796
+ __privateMethod(this, _ProductRecoCarouselLit_instances, stopWatching_fn).call(this);
7797
+ }
7875
7798
  render() {
7876
7799
  const items = this._parsed?.items ?? [];
7877
- if (items.length === 0) return html13``;
7878
- return html13`
7800
+ if (items.length === 0) return html14``;
7801
+ return html14`
7879
7802
  <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
7803
  <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
- ${items.map(
7882
- (it) => html13`
7804
+ ${items.map((it) => {
7805
+ const shownPrice = it.product_id && this._live[it.product_id] || it.price;
7806
+ const label = [it.name, shownPrice, it.hook].filter(Boolean).join(", ");
7807
+ return html14`
7883
7808
  <div role="listitem" style="display:contents;">
7884
7809
  <a data-reco-item data-product-cta="primary" href=${it.pdp_href}
7810
+ aria-label=${label || nothing9}
7885
7811
  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}
7812
+ ${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;">` : (
7813
+ // No photo from the store: the same branded stand-in the
7814
+ // card and comparison paint (imageStandin.ts), never a
7815
+ // flat grey box. Decorative: the product name is shown as
7816
+ // text below it. Nothing to name it by -> no monogram (a
7817
+ // '?' reads as broken).
7818
+ html14`<div data-product-image-standin aria-hidden="true" style="position:absolute;inset:0;background:${IMAGE_STANDIN_BACKGROUND};">${it.name || it.hook ? standinMonogram(it.name || it.hook) : nothing9}</div>`
7819
+ )}
7887
7820
  <span style="position:absolute;inset:auto 0 0 0;height:60%;background:linear-gradient(transparent,rgba(0,0,0,.62));"></span>
7888
7821
  <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}
7822
+ ${!it.image_url && it.name ? html14`<span data-reco-name style="display:block;font-weight:700;font-size:0.82rem;line-height:1.2;margin-bottom:2px;">${it.name}</span>` : nothing9}
7823
+ ${shownPrice ? html14`<span style="display:block;font-weight:700;font-size:0.9rem;">${shownPrice}</span>` : nothing9}
7824
+ ${it.hook ? html14`<span style="display:block;font-size:0.72rem;line-height:1.2;opacity:.95;">${it.hook}</span>` : nothing9}
7891
7825
  </span>
7892
7826
  </a>
7893
- </div>`
7894
- )}
7827
+ </div>`;
7828
+ })}
7895
7829
  </div>
7896
7830
  </section>`;
7897
7831
  }
7898
7832
  };
7833
+ _unwatch3 = new WeakMap();
7899
7834
  _ProductRecoCarouselLit_instances = new WeakSet();
7900
7835
  /** Measure only the SETTLED carousel (items rendered). An empty carousel
7901
7836
  * renders nothing and is never "blank". NON-PII stateKey. */
@@ -7911,7 +7846,30 @@ healthProbe_fn3 = function() {
7911
7846
  widgetKind: "adaptive-product:reco"
7912
7847
  };
7913
7848
  };
7914
- ProductRecoCarouselLit.properties = { props: { attribute: false }, _parsed: { state: true } };
7849
+ /** The store's live price for each item, now and whenever the runtime
7850
+ * publishes a commerce adapter; an item the store cannot price keeps the
7851
+ * catalog price the server sent. */
7852
+ watchPrices_fn = function() {
7853
+ __privateMethod(this, _ProductRecoCarouselLit_instances, stopWatching_fn).call(this);
7854
+ for (const item of this._parsed?.items ?? []) {
7855
+ const id = item.product_id;
7856
+ if (!id || !item.price) continue;
7857
+ __privateGet(this, _unwatch3).push(
7858
+ watchLivePrice(id, (live) => {
7859
+ this._live = { ...this._live, [id]: live.price };
7860
+ })
7861
+ );
7862
+ }
7863
+ };
7864
+ stopWatching_fn = function() {
7865
+ for (const stop of __privateGet(this, _unwatch3)) stop();
7866
+ __privateSet(this, _unwatch3, []);
7867
+ };
7868
+ ProductRecoCarouselLit.properties = {
7869
+ props: { attribute: false },
7870
+ _parsed: { state: true },
7871
+ _live: { state: true }
7872
+ };
7915
7873
  ProductRecoCarouselLit._testing = {
7916
7874
  healthProbe: (host) => {
7917
7875
  var _a;
@@ -7922,7 +7880,7 @@ if (!customElements.get("syntro-product-reco-carousel"))
7922
7880
  customElements.define("syntro-product-reco-carousel", ProductRecoCarouselLit);
7923
7881
 
7924
7882
  // src/widgets/PdpTrendingNewsLit.ts
7925
- import { html as html14, LitElement as LitElement13 } from "lit";
7883
+ import { html as html15, LitElement as LitElement14 } from "lit";
7926
7884
 
7927
7885
  // src/widgets/entrance.ts
7928
7886
  function armEntranceSettle(onSettle, container, settleMs, fastPath = false) {
@@ -7950,14 +7908,6 @@ function armEntranceSettle(onSettle, container, settleMs, fastPath = false) {
7950
7908
  }
7951
7909
 
7952
7910
  // 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
7911
  var TRENDING_NEWS_CSS = `
7962
7912
  .pdp-trending-news {
7963
7913
  --tn-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8163,11 +8113,12 @@ var TRENDING_NEWS_CSS = `
8163
8113
  outline: none;
8164
8114
  }
8165
8115
  .pdp-tn-headline-link::after {
8166
- content: ' \\2197';
8116
+ /* Decoration: empty alternative text keeps it out of the link's name. */
8117
+ content: ' \\2197' / '';
8167
8118
  font-family: var(--tn-sans);
8168
8119
  font-size: 0.65em;
8169
8120
  letter-spacing: 0;
8170
- color: var(--tn-primary);
8121
+ color: var(--syntro-pdp-primary-ink, var(--tn-primary));
8171
8122
  opacity: 0.55;
8172
8123
  vertical-align: 0.18em;
8173
8124
  margin-left: 0.2em;
@@ -8186,7 +8137,7 @@ var TRENDING_NEWS_CSS = `
8186
8137
  font-style: italic;
8187
8138
  font-size: 11px;
8188
8139
  letter-spacing: 0.06em;
8189
- color: color-mix(in srgb, var(--tn-primary) 70%, transparent);
8140
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--tn-primary) 70%, transparent));
8190
8141
  padding: 3px 9px 3px 8px;
8191
8142
  border-radius: 999px;
8192
8143
  background: color-mix(in srgb, var(--tn-primary) 12%, transparent);
@@ -8269,7 +8220,7 @@ var TRENDING_NEWS_CSS = `
8269
8220
  }
8270
8221
  }
8271
8222
  `;
8272
- var PdpTrendingNewsLit = class extends LitElement13 {
8223
+ var PdpTrendingNewsLit = class extends LitElement14 {
8273
8224
  constructor() {
8274
8225
  super(...arguments);
8275
8226
  this.data = void 0;
@@ -8282,7 +8233,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8282
8233
  }
8283
8234
  connectedCallback() {
8284
8235
  super.connectedCallback();
8285
- ensureTrendingNewsStyles();
8236
+ ensureStylesInRoot(this, "syntro-pdp-trending-news-style", TRENDING_NEWS_CSS);
8286
8237
  if (this._entering) this.requestUpdate();
8287
8238
  }
8288
8239
  disconnectedCallback() {
@@ -8305,7 +8256,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8305
8256
  }
8306
8257
  render() {
8307
8258
  if (!this.data) {
8308
- return html14`<div data-skeleton class="pdp-trending-news-skeleton">
8259
+ return html15`<div data-skeleton class="pdp-trending-news-skeleton">
8309
8260
  <div class="pdp-skeleton-bar" style="width:60%"></div>
8310
8261
  <div class="pdp-skeleton-bar" style="width:90%"></div>
8311
8262
  <div class="pdp-skeleton-bar" style="width:80%"></div>
@@ -8315,7 +8266,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8315
8266
  const items = this.data.items;
8316
8267
  const featured = items[0];
8317
8268
  const rest = items.slice(1);
8318
- return html14`
8269
+ return html15`
8319
8270
  <section
8320
8271
  class="${this._entering ? "pdp-trending-news is-entering" : "pdp-trending-news"}"
8321
8272
  aria-label="Trending news"
@@ -8332,7 +8283,7 @@ var PdpTrendingNewsLit = class extends LitElement13 {
8332
8283
  <p class="pdp-tn-blurb">${featured.blurb}</p>
8333
8284
  </article>
8334
8285
  ${rest.map(
8335
- (item) => html14`
8286
+ (item) => html15`
8336
8287
  <article class="pdp-tn-card">
8337
8288
  <div class="pdp-tn-meta">
8338
8289
  <span class="pdp-tn-dot" aria-hidden="true"></span>
@@ -8354,8 +8305,8 @@ PdpTrendingNewsLit.properties = {
8354
8305
  _entering: { state: true }
8355
8306
  };
8356
8307
  function renderHeadlineLink(item) {
8357
- if (!item.url) return html14`${item.headline}`;
8358
- return html14`<a
8308
+ if (!item.url) return html15`${item.headline}`;
8309
+ return html15`<a
8359
8310
  class="pdp-tn-headline-link"
8360
8311
  href=${item.url}
8361
8312
  target="_blank"
@@ -8368,15 +8319,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-tre
8368
8319
  }
8369
8320
 
8370
8321
  // 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
- }
8322
+ import { html as html16, LitElement as LitElement15 } from "lit";
8380
8323
  var MAGAZINE_CSS = `
8381
8324
  .pdp-tn-mag {
8382
8325
  --tn-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8548,7 +8491,7 @@ var MAGAZINE_CSS = `
8548
8491
  }
8549
8492
  }
8550
8493
  `;
8551
- var PdpTrendingNewsMagazineLit = class extends LitElement14 {
8494
+ var PdpTrendingNewsMagazineLit = class extends LitElement15 {
8552
8495
  constructor() {
8553
8496
  super(...arguments);
8554
8497
  this.data = void 0;
@@ -8558,17 +8501,17 @@ var PdpTrendingNewsMagazineLit = class extends LitElement14 {
8558
8501
  }
8559
8502
  connectedCallback() {
8560
8503
  super.connectedCallback();
8561
- ensureMagazineStyles();
8504
+ ensureStylesInRoot(this, "syntro-pdp-trending-news-magazine-style", MAGAZINE_CSS);
8562
8505
  }
8563
8506
  render() {
8564
8507
  if (!this.data) {
8565
- return html15`<div aria-busy="true" class="pdp-tn-mag" style="opacity:0.5;">…</div>`;
8508
+ return html16`<div aria-busy="true" class="pdp-tn-mag" style="opacity:0.5;">…</div>`;
8566
8509
  }
8567
8510
  const items = this.data.items;
8568
8511
  const [lead, ...rest] = items;
8569
- return html15`
8512
+ return html16`
8570
8513
  <section class="pdp-tn-mag" aria-label="Trending news">
8571
- ${lead ? html15`<article class="pdp-tn-mag__lead">
8514
+ ${lead ? html16`<article class="pdp-tn-mag__lead">
8572
8515
  <div class="pdp-tn-mag__meta">
8573
8516
  <span class="pdp-tn-mag__meta-source">${lead.source}</span>
8574
8517
  · ${lead.published_relative}
@@ -8577,7 +8520,7 @@ var PdpTrendingNewsMagazineLit = class extends LitElement14 {
8577
8520
  <p class="pdp-tn-mag__blurb">${lead.blurb}</p>
8578
8521
  </article>` : ""}
8579
8522
  ${rest.map(
8580
- (it) => html15`<article class="pdp-tn-mag__item">
8523
+ (it) => html16`<article class="pdp-tn-mag__item">
8581
8524
  <div class="pdp-tn-mag__meta">
8582
8525
  <span class="pdp-tn-mag__meta-source">${it.source}</span>
8583
8526
  · ${it.published_relative}
@@ -8594,8 +8537,8 @@ PdpTrendingNewsMagazineLit.properties = {
8594
8537
  data: { attribute: false }
8595
8538
  };
8596
8539
  function renderHeadlineLink2(item) {
8597
- if (!item.url) return html15`${item.headline}`;
8598
- return html15`<a
8540
+ if (!item.url) return html16`${item.headline}`;
8541
+ return html16`<a
8599
8542
  class="pdp-tn-mag__headline-link"
8600
8543
  href=${item.url}
8601
8544
  target="_blank"
@@ -8608,15 +8551,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-tre
8608
8551
  }
8609
8552
 
8610
8553
  // 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
- }
8554
+ import { html as html17, LitElement as LitElement16 } from "lit";
8620
8555
  var HERO_CSS = `
8621
8556
  .pdp-tn-hero {
8622
8557
  --tn-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8799,7 +8734,7 @@ var HERO_CSS = `
8799
8734
  }
8800
8735
  }
8801
8736
  `;
8802
- var PdpTrendingNewsHeroLit = class extends LitElement15 {
8737
+ var PdpTrendingNewsHeroLit = class extends LitElement16 {
8803
8738
  constructor() {
8804
8739
  super(...arguments);
8805
8740
  this.data = void 0;
@@ -8809,15 +8744,15 @@ var PdpTrendingNewsHeroLit = class extends LitElement15 {
8809
8744
  }
8810
8745
  connectedCallback() {
8811
8746
  super.connectedCallback();
8812
- ensureHeroStyles();
8747
+ ensureStylesInRoot(this, "syntro-pdp-trending-news-hero-style", HERO_CSS);
8813
8748
  }
8814
8749
  render() {
8815
8750
  if (!this.data) {
8816
- return html16`<div aria-busy="true" class="pdp-tn-hero" style="opacity:0.5;">…</div>`;
8751
+ return html17`<div aria-busy="true" class="pdp-tn-hero" style="opacity:0.5;">…</div>`;
8817
8752
  }
8818
8753
  const [lead, ...rest] = this.data.items;
8819
- if (!lead) return html16``;
8820
- return html16`
8754
+ if (!lead) return html17``;
8755
+ return html17`
8821
8756
  <section class="pdp-tn-hero" aria-label="Trending news">
8822
8757
  <article class="pdp-tn-hero__panel">
8823
8758
  <blockquote class="pdp-tn-hero__quote">${lead.blurb}</blockquote>
@@ -8828,9 +8763,9 @@ var PdpTrendingNewsHeroLit = class extends LitElement15 {
8828
8763
  </footer>
8829
8764
  <p class="pdp-tn-hero__headline">${renderHeadlineLink3(lead)}</p>
8830
8765
  </article>
8831
- ${rest.length ? html16`<div class="pdp-tn-hero__followon">
8766
+ ${rest.length ? html17`<div class="pdp-tn-hero__followon">
8832
8767
  ${rest.map(
8833
- (it) => html16`<div class="pdp-tn-hero__row">
8768
+ (it) => html17`<div class="pdp-tn-hero__row">
8834
8769
  <p class="pdp-tn-hero__row-headline">${renderHeadlineLink3(it)}</p>
8835
8770
  <span class="pdp-tn-hero__row-meta">${it.source}</span>
8836
8771
  </div>`
@@ -8844,8 +8779,8 @@ PdpTrendingNewsHeroLit.properties = {
8844
8779
  data: { attribute: false }
8845
8780
  };
8846
8781
  function renderHeadlineLink3(item) {
8847
- if (!item.url) return html16`${item.headline}`;
8848
- return html16`<a
8782
+ if (!item.url) return html17`${item.headline}`;
8783
+ return html17`<a
8849
8784
  class="pdp-tn-hero__headline-link"
8850
8785
  href=${item.url}
8851
8786
  target="_blank"
@@ -8858,15 +8793,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-tre
8858
8793
  }
8859
8794
 
8860
8795
  // 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
- }
8796
+ import { html as html18, LitElement as LitElement17 } from "lit";
8870
8797
  var MASONRY_CSS = `
8871
8798
  .pdp-pf-mas {
8872
8799
  --pf-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -8997,12 +8924,12 @@ var MASONRY_CSS = `
8997
8924
  padding-top: 10px;
8998
8925
  }
8999
8926
  `;
9000
- function personaHue(s4) {
8927
+ function personaHue(s) {
9001
8928
  let h = 0;
9002
- for (let i2 = 0; i2 < s4.length; i2++) h = h * 31 + s4.charCodeAt(i2) | 0;
8929
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
9003
8930
  return Math.abs(h) % 360;
9004
8931
  }
9005
- var PdpPeerFeedMasonryLit = class extends LitElement16 {
8932
+ var PdpPeerFeedMasonryLit = class extends LitElement17 {
9006
8933
  constructor() {
9007
8934
  super(...arguments);
9008
8935
  this.data = void 0;
@@ -9012,17 +8939,17 @@ var PdpPeerFeedMasonryLit = class extends LitElement16 {
9012
8939
  }
9013
8940
  connectedCallback() {
9014
8941
  super.connectedCallback();
9015
- ensureMasonryStyles();
8942
+ ensureStylesInRoot(this, "syntro-pdp-peer-feed-masonry-style", MASONRY_CSS);
9016
8943
  }
9017
8944
  render() {
9018
8945
  if (!this.data) {
9019
- return html17`<div aria-busy="true" class="pdp-pf-mas" style="opacity:0.5;">…</div>`;
8946
+ return html18`<div aria-busy="true" class="pdp-pf-mas" style="opacity:0.5;">…</div>`;
9020
8947
  }
9021
- return html17`
8948
+ return html18`
9022
8949
  <section class="pdp-pf-mas" aria-label="Peer voices">
9023
8950
  <div class="pdp-pf-mas__grid">
9024
8951
  ${this.data.peers.map(
9025
- (p) => html17`<article
8952
+ (p) => html18`<article
9026
8953
  class="pdp-pf-mas__card"
9027
8954
  style=${`--pf-avatar-hue: ${personaHue(p.persona)};`}
9028
8955
  >
@@ -9050,15 +8977,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-pee
9050
8977
  }
9051
8978
 
9052
8979
  // 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
- }
8980
+ import { html as html19, LitElement as LitElement18, svg } from "lit";
9062
8981
  var CAROUSEL_CSS2 = `
9063
8982
  /* The custom-element tag itself + its slot-mount parent must declare
9064
8983
  display:block AND min-width:0 so they participate in the grid cell's
@@ -9227,12 +9146,12 @@ syntro-pdp-peer-feed-carousel {
9227
9146
  var GLYPHS = ["waveform", "gauge", "leaf", "spark"];
9228
9147
  function glyphFor(persona) {
9229
9148
  let h = 0;
9230
- for (let i2 = 0; i2 < persona.length; i2++) h = h * 31 + persona.charCodeAt(i2) | 0;
9149
+ for (let i = 0; i < persona.length; i++) h = h * 31 + persona.charCodeAt(i) | 0;
9231
9150
  return GLYPHS[Math.abs(h) % GLYPHS.length];
9232
9151
  }
9233
9152
  function glyphHue(persona) {
9234
9153
  let h = 0;
9235
- for (let i2 = 0; i2 < persona.length; i2++) h = h * 17 + persona.charCodeAt(i2) | 0;
9154
+ for (let i = 0; i < persona.length; i++) h = h * 17 + persona.charCodeAt(i) | 0;
9236
9155
  return Math.abs(h) % 360;
9237
9156
  }
9238
9157
  function renderGlyph(kind) {
@@ -9266,7 +9185,7 @@ function renderGlyph(kind) {
9266
9185
  <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
9186
  </svg>`;
9268
9187
  }
9269
- var PdpPeerFeedCarouselLit = class extends LitElement17 {
9188
+ var PdpPeerFeedCarouselLit = class extends LitElement18 {
9270
9189
  constructor() {
9271
9190
  super(...arguments);
9272
9191
  this.data = void 0;
@@ -9276,19 +9195,19 @@ var PdpPeerFeedCarouselLit = class extends LitElement17 {
9276
9195
  }
9277
9196
  connectedCallback() {
9278
9197
  super.connectedCallback();
9279
- ensureCarouselStyles2();
9198
+ ensureStylesInRoot(this, "syntro-pdp-peer-feed-carousel-style", CAROUSEL_CSS2);
9280
9199
  }
9281
9200
  render() {
9282
9201
  if (!this.data) {
9283
- return html18`<div aria-busy="true" class="pdp-pf-car" style="opacity:0.5;">…</div>`;
9202
+ return html19`<div aria-busy="true" class="pdp-pf-car" style="opacity:0.5;">…</div>`;
9284
9203
  }
9285
- return html18`
9204
+ return html19`
9286
9205
  <section class="pdp-pf-car" aria-label="Peer voices">
9287
9206
  <div class="pdp-pf-car__track">
9288
9207
  ${this.data.peers.map((p) => {
9289
9208
  const kind = glyphFor(p.persona);
9290
9209
  const hue = glyphHue(p.persona);
9291
- return html18`<article class="pdp-pf-car__card" style=${`--pf-glyph-hue: ${hue};`}>
9210
+ return html19`<article class="pdp-pf-car__card" style=${`--pf-glyph-hue: ${hue};`}>
9292
9211
  <span class="pdp-pf-car__glyph" aria-hidden="true">${renderGlyph(kind)}</span>
9293
9212
  <span class="pdp-pf-car__persona">${p.persona}</span>
9294
9213
  <blockquote class="pdp-pf-car__quote">${p.quote}</blockquote>
@@ -9313,7 +9232,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-pee
9313
9232
  }
9314
9233
 
9315
9234
  // src/widgets/PdpChartLit.ts
9316
- import { html as html19, LitElement as LitElement18, nothing as nothing9, svg as svg2 } from "lit";
9235
+ import { html as html20, LitElement as LitElement19, nothing as nothing10, svg as svg2 } from "lit";
9317
9236
  function formatNum(v) {
9318
9237
  if (v === void 0 || !Number.isFinite(v)) return "\u2014";
9319
9238
  if (Number.isInteger(v)) return v.toLocaleString("en-US");
@@ -9323,7 +9242,7 @@ function formatNum(v) {
9323
9242
  return decPart ? `${formatted}.${decPart}` : formatted;
9324
9243
  }
9325
9244
  function buildComparisonMatrix(data) {
9326
- const colLabels = data.series.map((s4) => s4.name);
9245
+ const colLabels = data.series.map((s) => s.name);
9327
9246
  const seenRows = /* @__PURE__ */ new Map();
9328
9247
  const rowLabels = [];
9329
9248
  for (const series of data.series) {
@@ -9391,7 +9310,7 @@ function niceTicks(yMax) {
9391
9310
  const rawStep = yMax / targetCount;
9392
9311
  const mag = 10 ** Math.floor(Math.log10(rawStep));
9393
9312
  const niceSteps = [1, 2, 2.5, 5, 10];
9394
- const step = niceSteps.find((s4) => s4 * mag >= rawStep);
9313
+ const step = niceSteps.find((s) => s * mag >= rawStep);
9395
9314
  const niceStep = step * mag;
9396
9315
  const ticks = [];
9397
9316
  for (let v = 0; v <= yMax + niceStep * 1e-3; v += niceStep) {
@@ -9402,8 +9321,8 @@ function niceTicks(yMax) {
9402
9321
  function buildLinePaths(data) {
9403
9322
  const seenX = /* @__PURE__ */ new Map();
9404
9323
  const xLabels = [];
9405
- for (const s4 of data.series) {
9406
- for (const p of s4.points) {
9324
+ for (const s of data.series) {
9325
+ for (const p of s.points) {
9407
9326
  if (!seenX.has(p.x)) {
9408
9327
  seenX.set(p.x, xLabels.length);
9409
9328
  xLabels.push(p.x);
@@ -9412,16 +9331,16 @@ function buildLinePaths(data) {
9412
9331
  }
9413
9332
  const denom = Math.max(1, xLabels.length - 1);
9414
9333
  let yMax = 0;
9415
- for (const s4 of data.series) {
9416
- for (const p of s4.points) {
9334
+ for (const s of data.series) {
9335
+ for (const p of s.points) {
9417
9336
  if (p.y > yMax) yMax = p.y;
9418
9337
  }
9419
9338
  }
9420
9339
  if (yMax === 0) yMax = 1;
9421
- const seriesOut = data.series.map((s4, sIdx) => ({
9422
- name: s4.name,
9340
+ const seriesOut = data.series.map((s, sIdx) => ({
9341
+ name: s.name,
9423
9342
  seriesIdx: sIdx,
9424
- points: s4.points.map((p) => {
9343
+ points: s.points.map((p) => {
9425
9344
  const xIdx = seenX.get(p.x);
9426
9345
  return {
9427
9346
  cx: xIdx / denom * 100,
@@ -9433,14 +9352,6 @@ function buildLinePaths(data) {
9433
9352
  }));
9434
9353
  return { series: seriesOut, xLabels, yMax };
9435
9354
  }
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
9355
  var CHART_CSS = `
9445
9356
  .pdp-chart-frame {
9446
9357
  --pc-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -9808,12 +9719,12 @@ var CHART_CSS = `
9808
9719
  background: linear-gradient(90deg, color-mix(in srgb, var(--pc-primary) 12%, transparent), transparent 80%);
9809
9720
  }
9810
9721
  .pdp-chart-table tbody tr.is-winning td:first-child {
9811
- color: color-mix(in srgb, var(--pc-primary) 70%, white 30%);
9722
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pc-primary) 70%, white 30%));
9812
9723
  }
9813
9724
  .pdp-chart-table tbody tr.is-winning td:first-child::before {
9814
9725
  content: '\\2726 ';
9815
9726
  font-size: 11px;
9816
- color: color-mix(in srgb, var(--pc-primary) 60%, white 40%);
9727
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pc-primary) 60%, white 40%));
9817
9728
  vertical-align: 1px;
9818
9729
  margin-right: 2px;
9819
9730
  }
@@ -9936,7 +9847,7 @@ function colorVar(seriesIdx) {
9936
9847
  return `var(--pc-color-${seriesIdx % PALETTE_SIZE})`;
9937
9848
  }
9938
9849
  var _PdpChartLit_instances, renderLegend_fn, renderTable_fn, renderBars_fn, renderLine_fn;
9939
- var PdpChartLit = class extends LitElement18 {
9850
+ var PdpChartLit = class extends LitElement19 {
9940
9851
  constructor() {
9941
9852
  super(...arguments);
9942
9853
  __privateAdd(this, _PdpChartLit_instances);
@@ -9949,7 +9860,7 @@ var PdpChartLit = class extends LitElement18 {
9949
9860
  }
9950
9861
  connectedCallback() {
9951
9862
  super.connectedCallback();
9952
- ensureChartStyles();
9863
+ ensureStylesInRoot(this, "syntro-pdp-chart-style", CHART_CSS);
9953
9864
  if (this._entering) this.requestUpdate();
9954
9865
  }
9955
9866
  disconnectedCallback() {
@@ -9972,16 +9883,16 @@ var PdpChartLit = class extends LitElement18 {
9972
9883
  }
9973
9884
  render() {
9974
9885
  if (!this.data) {
9975
- return html19`<div data-skeleton class="pdp-chart-skeleton">
9886
+ return html20`<div data-skeleton class="pdp-chart-skeleton">
9976
9887
  <div class="pdp-skeleton-bar" style="width:50%"></div>
9977
9888
  <div class="pdp-skeleton-rect" style="height:200px"></div>
9978
9889
  </div>`;
9979
9890
  }
9980
9891
  const isTable = this.data.viz_kind === "table";
9981
- const allSeriesOnePoint = this.data.series.every((s4) => s4.points.length <= 1);
9892
+ const allSeriesOnePoint = this.data.series.every((s) => s.points.length <= 1);
9982
9893
  const effectiveKind = isTable ? "table" : this.data.viz_kind === "line" && !allSeriesOnePoint ? "line" : "bar";
9983
9894
  const isMultiSeries = this.data.series.length > 1;
9984
- return html19`
9895
+ return html20`
9985
9896
  <section
9986
9897
  class="${this._entering ? "pdp-chart pdp-chart-frame is-entering" : "pdp-chart pdp-chart-frame"}"
9987
9898
  aria-label="${this.data.title}"
@@ -9992,11 +9903,11 @@ var PdpChartLit = class extends LitElement18 {
9992
9903
  </div>
9993
9904
  <h3 class="pdp-chart-title">${this.data.title}</h3>
9994
9905
 
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}
9906
+ ${effectiveKind === "table" ? __privateMethod(this, _PdpChartLit_instances, renderTable_fn).call(this) : nothing10}
9907
+ ${effectiveKind === "bar" ? __privateMethod(this, _PdpChartLit_instances, renderBars_fn).call(this) : nothing10}
9908
+ ${effectiveKind === "line" ? __privateMethod(this, _PdpChartLit_instances, renderLine_fn).call(this) : nothing10}
9909
+ ${effectiveKind !== "table" && isMultiSeries ? __privateMethod(this, _PdpChartLit_instances, renderLegend_fn).call(this) : nothing10}
9910
+ ${this.data.rationale ? html20`<p class="pdp-chart-rationale">${this.data.rationale}</p>` : nothing10}
10000
9911
  </section>
10001
9912
  `;
10002
9913
  }
@@ -10004,17 +9915,17 @@ var PdpChartLit = class extends LitElement18 {
10004
9915
  _PdpChartLit_instances = new WeakSet();
10005
9916
  renderLegend_fn = function() {
10006
9917
  const data = this.data;
10007
- return html19`
9918
+ return html20`
10008
9919
  <ol class="pdp-chart-legend" aria-label="Legend">
10009
9920
  ${data.series.map(
10010
- (s4, idx) => html19`
9921
+ (s, idx) => html20`
10011
9922
  <li class="pdp-chart-legend-item">
10012
9923
  <span
10013
9924
  class="pdp-chart-legend-swatch"
10014
9925
  style="background:${colorVar(idx)}"
10015
9926
  aria-hidden="true"
10016
9927
  ></span>
10017
- <span>${s4.name}</span>
9928
+ <span>${s.name}</span>
10018
9929
  </li>
10019
9930
  `
10020
9931
  )}
@@ -10023,22 +9934,22 @@ renderLegend_fn = function() {
10023
9934
  };
10024
9935
  renderTable_fn = function() {
10025
9936
  const matrix = buildComparisonMatrix(this.data);
10026
- return html19`
9937
+ return html20`
10027
9938
  <div class="pdp-chart-table-wrap">
10028
9939
  <table class="pdp-chart-table">
10029
9940
  <thead>
10030
9941
  <tr>
10031
9942
  <th></th>
10032
- ${matrix.colLabels.map((label) => html19`<th>${label}</th>`)}
9943
+ ${matrix.colLabels.map((label) => html20`<th>${label}</th>`)}
10033
9944
  </tr>
10034
9945
  </thead>
10035
9946
  <tbody>
10036
9947
  ${matrix.rowLabels.map(
10037
- (row, rowIdx) => html19`
9948
+ (row, rowIdx) => html20`
10038
9949
  <tr class=${rowIdx === matrix.winningRow ? "is-winning" : ""}>
10039
9950
  <td>${row}</td>
10040
9951
  ${matrix.cells[rowIdx].map(
10041
- (cell) => html19`<td class="pdp-chart-numeric-cell">${formatNum(cell)}</td>`
9952
+ (cell) => html20`<td class="pdp-chart-numeric-cell">${formatNum(cell)}</td>`
10042
9953
  )}
10043
9954
  </tr>
10044
9955
  `
@@ -10053,7 +9964,7 @@ renderBars_fn = function() {
10053
9964
  const { buckets, yMax } = buildBarBuckets(data);
10054
9965
  const ticks = niceTicks(yMax);
10055
9966
  const scaleMax = Math.max(yMax, ticks[ticks.length - 1]);
10056
- return html19`
9967
+ return html20`
10057
9968
  <div
10058
9969
  class="pdp-chart-bars"
10059
9970
  role="figure"
@@ -10062,33 +9973,33 @@ renderBars_fn = function() {
10062
9973
  >
10063
9974
  <div class="pdp-chart-yaxis" aria-hidden="true">
10064
9975
  ${ticks.map(
10065
- (t2) => html19`
9976
+ (t) => html20`
10066
9977
  <span
10067
9978
  class="pdp-chart-yaxis-tick"
10068
- style="bottom:${t2 / scaleMax * 100}%"
9979
+ style="bottom:${t / scaleMax * 100}%"
10069
9980
  >
10070
- ${formatNum(t2)}
9981
+ ${formatNum(t)}
10071
9982
  </span>
10072
9983
  `
10073
9984
  )}
10074
9985
  </div>
10075
9986
  <div class="pdp-chart-bars-area">
10076
9987
  ${ticks.map(
10077
- (t2) => html19`
9988
+ (t) => html20`
10078
9989
  <div
10079
9990
  class="pdp-chart-bars-gridline"
10080
- style="bottom:${t2 / scaleMax * 100}%"
9991
+ style="bottom:${t / scaleMax * 100}%"
10081
9992
  aria-hidden="true"
10082
9993
  ></div>
10083
9994
  `
10084
9995
  )}
10085
9996
  <div class="pdp-chart-bars-baseline" aria-hidden="true"></div>
10086
9997
  ${buckets.map(
10087
- (bucket, bIdx) => html19`
9998
+ (bucket, bIdx) => html20`
10088
9999
  <div class="pdp-chart-bars-bucket">
10089
10000
  <div class="pdp-chart-bars-stack">
10090
10001
  ${bucket.bars.map(
10091
- (bar, barIdxInBucket) => html19`
10002
+ (bar, barIdxInBucket) => html20`
10092
10003
  <div
10093
10004
  class="pdp-chart-bar"
10094
10005
  style="
@@ -10099,9 +10010,9 @@ renderBars_fn = function() {
10099
10010
  role="img"
10100
10011
  aria-label="${bucket.label} — ${bar.seriesName}: ${formatNum(bar.value)}"
10101
10012
  >
10102
- ${bucket.bars.length === 1 ? html19`<span class="pdp-chart-bar-value"
10013
+ ${bucket.bars.length === 1 ? html20`<span class="pdp-chart-bar-value"
10103
10014
  >${formatNum(bar.value)}</span
10104
- >` : nothing9}
10015
+ >` : nothing10}
10105
10016
  </div>
10106
10017
  `
10107
10018
  )}
@@ -10119,7 +10030,7 @@ renderLine_fn = function() {
10119
10030
  const { series, xLabels, yMax } = buildLinePaths(data);
10120
10031
  const ticks = niceTicks(yMax);
10121
10032
  const scaleMax = Math.max(yMax, ticks[ticks.length - 1]);
10122
- return html19`
10033
+ return html20`
10123
10034
  <div
10124
10035
  class="pdp-chart-line"
10125
10036
  role="figure"
@@ -10129,12 +10040,12 @@ renderLine_fn = function() {
10129
10040
  >
10130
10041
  <div class="pdp-chart-line-yaxis" aria-hidden="true">
10131
10042
  ${ticks.map(
10132
- (t2) => html19`
10043
+ (t) => html20`
10133
10044
  <span
10134
10045
  class="pdp-chart-yaxis-tick"
10135
- style="bottom:${t2 / scaleMax * 100}%"
10046
+ style="bottom:${t / scaleMax * 100}%"
10136
10047
  >
10137
- ${formatNum(t2)}
10048
+ ${formatNum(t)}
10138
10049
  </span>
10139
10050
  `
10140
10051
  )}
@@ -10146,25 +10057,25 @@ renderLine_fn = function() {
10146
10057
  preserveAspectRatio="none"
10147
10058
  aria-hidden="true"
10148
10059
  >
10149
- ${ticks.map((t2) => {
10150
- const y = 60 - t2 / scaleMax * 56 - 2;
10060
+ ${ticks.map((t) => {
10061
+ const y = 60 - t / scaleMax * 56 - 2;
10151
10062
  return svg2`<line class="pdp-chart-line-gridline" x1="0" y1=${y} x2="100" y2=${y} />`;
10152
10063
  })}
10153
10064
  ${series.map(
10154
- (s4) => svg2`
10065
+ (s) => svg2`
10155
10066
  <polyline
10156
10067
  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(" ")}
10068
+ style=${`--line-color:${colorVar(s.seriesIdx)}; --delay:${s.seriesIdx * 200}ms`}
10069
+ points=${s.points.map((p) => `${p.cx},${p.cy}`).join(" ")}
10159
10070
  />
10160
- ${s4.points.map(
10071
+ ${s.points.map(
10161
10072
  (p, idx) => svg2`
10162
10073
  <circle
10163
10074
  class="pdp-chart-line-dot"
10164
10075
  cx=${p.cx}
10165
10076
  cy=${p.cy}
10166
10077
  r="1.4"
10167
- style=${`--line-color:${colorVar(s4.seriesIdx)}; --delay:${s4.seriesIdx * 200}ms; --dot-delay:${800 + idx * 60}ms`}
10078
+ style=${`--line-color:${colorVar(s.seriesIdx)}; --delay:${s.seriesIdx * 200}ms; --dot-delay:${800 + idx * 60}ms`}
10168
10079
  />
10169
10080
  `
10170
10081
  )}
@@ -10178,7 +10089,7 @@ renderLine_fn = function() {
10178
10089
  style="--col-count:${xLabels.length}"
10179
10090
  aria-hidden="true"
10180
10091
  >
10181
- ${xLabels.map((label) => html19`<span>${label}</span>`)}
10092
+ ${xLabels.map((label) => html20`<span>${label}</span>`)}
10182
10093
  </div>
10183
10094
  `;
10184
10095
  };
@@ -10191,15 +10102,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-cha
10191
10102
  }
10192
10103
 
10193
10104
  // 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
- }
10105
+ import { html as html21, LitElement as LitElement20 } from "lit";
10203
10106
  var CSS = `
10204
10107
  .pdp-chart-bar {
10205
10108
  --c-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10279,7 +10182,7 @@ var CSS = `
10279
10182
  }
10280
10183
  `;
10281
10184
  var PALETTE = ["hsl(150 32% 50%)", "hsl(40 75% 60%)", "hsl(208 50% 55%)", "hsl(320 38% 60%)"];
10282
- var PdpChartBarStackedLit = class extends LitElement19 {
10185
+ var PdpChartBarStackedLit = class extends LitElement20 {
10283
10186
  constructor() {
10284
10187
  super(...arguments);
10285
10188
  this.data = void 0;
@@ -10289,39 +10192,39 @@ var PdpChartBarStackedLit = class extends LitElement19 {
10289
10192
  }
10290
10193
  connectedCallback() {
10291
10194
  super.connectedCallback();
10292
- ensureStyles();
10195
+ ensureStylesInRoot(this, "syntro-pdp-chart-bar-style", CSS);
10293
10196
  }
10294
10197
  render() {
10295
- if (!this.data) return html20`<div class="pdp-chart-bar" style="opacity:0.5;">…</div>`;
10198
+ if (!this.data) return html21`<div class="pdp-chart-bar" style="opacity:0.5;">…</div>`;
10296
10199
  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`
10200
+ const allX = Array.from(new Set(series.flatMap((s) => s.points.map((p) => p.x))));
10201
+ const max = Math.max(...series.flatMap((s) => s.points.map((p) => p.y)), 1);
10202
+ return html21`
10300
10203
  <section class="pdp-chart-bar" aria-label=${title}>
10301
10204
  <h3 class="pdp-chart-bar__title">${title}</h3>
10302
- ${series.length > 1 ? html20`<div class="pdp-chart-bar__legend">
10205
+ ${series.length > 1 ? html21`<div class="pdp-chart-bar__legend">
10303
10206
  ${series.map(
10304
- (s4, i2) => html20`<span
10207
+ (s, i) => html21`<span
10305
10208
  ><span
10306
10209
  class="pdp-chart-bar__legend-dot"
10307
- style=${`background: ${PALETTE[i2 % PALETTE.length]};`}
10210
+ style=${`background: ${PALETTE[i % PALETTE.length]};`}
10308
10211
  ></span
10309
- >${s4.name}</span
10212
+ >${s.name}</span
10310
10213
  >`
10311
10214
  )}
10312
10215
  </div>` : ""}
10313
10216
  ${allX.map(
10314
- (x) => html20`<div class="pdp-chart-bar__row">
10217
+ (x) => html21`<div class="pdp-chart-bar__row">
10315
10218
  <div class="pdp-chart-bar__row-label">${x}</div>
10316
10219
  <div class="pdp-chart-bar__row-bars">
10317
- ${series.map((s4, i2) => {
10318
- const point = s4.points.find((p) => p.x === x);
10220
+ ${series.map((s, i) => {
10221
+ const point = s.points.find((p) => p.x === x);
10319
10222
  if (!point) return "";
10320
10223
  const pct = point.y / max * 100;
10321
- return html20`<div class="pdp-chart-bar__bar-track">
10224
+ return html21`<div class="pdp-chart-bar__bar-track">
10322
10225
  <div
10323
10226
  class="pdp-chart-bar__bar-fill"
10324
- style=${`width: ${pct.toFixed(1)}%; background: ${PALETTE[i2 % PALETTE.length]};`}
10227
+ style=${`width: ${pct.toFixed(1)}%; background: ${PALETTE[i % PALETTE.length]};`}
10325
10228
  ></div>
10326
10229
  <span class="pdp-chart-bar__bar-value">${point.y}</span>
10327
10230
  </div>`;
@@ -10341,15 +10244,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-cha
10341
10244
  }
10342
10245
 
10343
10246
  // 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
- }
10247
+ import { html as html22, LitElement as LitElement21, svg as svg3 } from "lit";
10353
10248
  var CSS2 = `
10354
10249
  .pdp-chart-met {
10355
10250
  --c-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10443,17 +10338,17 @@ var CSS2 = `
10443
10338
  function buildSparkline(points) {
10444
10339
  const w = 200;
10445
10340
  const h = 38;
10446
- const xs = points.map((_, i2) => i2 / Math.max(points.length - 1, 1) * (w - 4) + 2);
10341
+ const xs = points.map((_, i) => i / Math.max(points.length - 1, 1) * (w - 4) + 2);
10447
10342
  const min = Math.min(...points.map((p) => p.y));
10448
10343
  const max = Math.max(...points.map((p) => p.y));
10449
10344
  const range = max - min || 1;
10450
10345
  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(" ");
10346
+ const line = xs.map((x, i) => `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${ys[i].toFixed(1)}`).join(" ");
10452
10347
  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);
10348
+ const peakIndex = points.reduce((best, p, i) => p.y > points[best].y ? i : best, 0);
10454
10349
  return { linePath: line, areaPath: area, peakIndex };
10455
10350
  }
10456
- var PdpChartMetricPullLit = class extends LitElement20 {
10351
+ var PdpChartMetricPullLit = class extends LitElement21 {
10457
10352
  constructor() {
10458
10353
  super(...arguments);
10459
10354
  this.data = void 0;
@@ -10463,16 +10358,16 @@ var PdpChartMetricPullLit = class extends LitElement20 {
10463
10358
  }
10464
10359
  connectedCallback() {
10465
10360
  super.connectedCallback();
10466
- ensureStyles2();
10361
+ ensureStylesInRoot(this, "syntro-pdp-chart-metric-style", CSS2);
10467
10362
  }
10468
10363
  render() {
10469
- if (!this.data) return html21`<div class="pdp-chart-met" style="opacity:0.5;">…</div>`;
10364
+ if (!this.data) return html22`<div class="pdp-chart-met" style="opacity:0.5;">…</div>`;
10470
10365
  const { title, series } = this.data;
10471
10366
  const first = series[0];
10472
- if (!first || first.points.length === 0) return html21``;
10367
+ if (!first || first.points.length === 0) return html22``;
10473
10368
  const points = first.points;
10474
10369
  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;
10370
+ const mean = points.reduce((s, p) => s + p.y, 0) / points.length;
10476
10371
  const delta = (peak.y - mean) / Math.max(mean, 1) * 100;
10477
10372
  const spark = buildSparkline(points);
10478
10373
  const w = 200;
@@ -10482,7 +10377,7 @@ var PdpChartMetricPullLit = class extends LitElement20 {
10482
10377
  const max = Math.max(...points.map((p) => p.y));
10483
10378
  const range = max - min || 1;
10484
10379
  const py = h - 4 - (peak.y - min) / range * (h - 8);
10485
- return html21`
10380
+ return html22`
10486
10381
  <section class="pdp-chart-met" aria-label=${title}>
10487
10382
  <div class="pdp-chart-met__metric">
10488
10383
  <span class="pdp-chart-met__big">${peak.y}</span>
@@ -10514,15 +10409,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-cha
10514
10409
  }
10515
10410
 
10516
10411
  // 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
- }
10412
+ import { html as html23, LitElement as LitElement22 } from "lit";
10526
10413
  var CSS3 = `
10527
10414
  .pdp-qa-acc {
10528
10415
  --qa-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10596,7 +10483,7 @@ var CSS3 = `
10596
10483
  max-width: 56ch;
10597
10484
  }
10598
10485
  `;
10599
- var PdpQaAccordionLit = class extends LitElement21 {
10486
+ var PdpQaAccordionLit = class extends LitElement22 {
10600
10487
  constructor() {
10601
10488
  super(...arguments);
10602
10489
  this.data = void 0;
@@ -10606,14 +10493,14 @@ var PdpQaAccordionLit = class extends LitElement21 {
10606
10493
  }
10607
10494
  connectedCallback() {
10608
10495
  super.connectedCallback();
10609
- ensureStyles3();
10496
+ ensureStylesInRoot(this, "syntro-pdp-qa-accordion-style", CSS3);
10610
10497
  }
10611
10498
  render() {
10612
- if (!this.data) return html22`<div class="pdp-qa-acc" style="opacity:0.5;">…</div>`;
10613
- return html22`
10499
+ if (!this.data) return html23`<div class="pdp-qa-acc" style="opacity:0.5;">…</div>`;
10500
+ return html23`
10614
10501
  <section class="pdp-qa-acc" aria-label="Frequently asked questions">
10615
10502
  ${this.data.items.map(
10616
- (it) => html22`<details class="pdp-qa-acc__item">
10503
+ (it) => html23`<details class="pdp-qa-acc__item">
10617
10504
  <summary>
10618
10505
  <span class="pdp-qa-acc__chev" aria-hidden="true"></span>
10619
10506
  <span>${it.question}</span>
@@ -10633,15 +10520,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-qa-
10633
10520
  }
10634
10521
 
10635
10522
  // 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
- }
10523
+ import { html as html24, LitElement as LitElement23 } from "lit";
10645
10524
  var CSS4 = `
10646
10525
  .pdp-qa-side {
10647
10526
  --qa-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10707,7 +10586,7 @@ var CSS4 = `
10707
10586
  margin: 0;
10708
10587
  }
10709
10588
  `;
10710
- var PdpQaSideBySideLit = class extends LitElement22 {
10589
+ var PdpQaSideBySideLit = class extends LitElement23 {
10711
10590
  constructor() {
10712
10591
  super(...arguments);
10713
10592
  this.data = void 0;
@@ -10717,14 +10596,14 @@ var PdpQaSideBySideLit = class extends LitElement22 {
10717
10596
  }
10718
10597
  connectedCallback() {
10719
10598
  super.connectedCallback();
10720
- ensureStyles4();
10599
+ ensureStylesInRoot(this, "syntro-pdp-qa-side-style", CSS4);
10721
10600
  }
10722
10601
  render() {
10723
- if (!this.data) return html23`<div class="pdp-qa-side" style="opacity:0.5;">…</div>`;
10724
- return html23`
10602
+ if (!this.data) return html24`<div class="pdp-qa-side" style="opacity:0.5;">…</div>`;
10603
+ return html24`
10725
10604
  <section class="pdp-qa-side" aria-label="Q&A">
10726
10605
  ${this.data.items.map(
10727
- (it) => html23`<div class="pdp-qa-side__item">
10606
+ (it) => html24`<div class="pdp-qa-side__item">
10728
10607
  <h4 class="pdp-qa-side__q">${it.question}</h4>
10729
10608
  <p class="pdp-qa-side__a">${it.answer}</p>
10730
10609
  </div>`
@@ -10741,15 +10620,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-qa-
10741
10620
  }
10742
10621
 
10743
10622
  // 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
- }
10623
+ import { html as html25, LitElement as LitElement24 } from "lit";
10753
10624
  var CSS5 = `
10754
10625
  .pdp-reg-fb {
10755
10626
  --r-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10832,7 +10703,7 @@ var CSS5 = `
10832
10703
  font-style: italic;
10833
10704
  }
10834
10705
  `;
10835
- var PdpRegionalFullBleedLit = class extends LitElement23 {
10706
+ var PdpRegionalFullBleedLit = class extends LitElement24 {
10836
10707
  constructor() {
10837
10708
  super(...arguments);
10838
10709
  this.data = void 0;
@@ -10842,12 +10713,12 @@ var PdpRegionalFullBleedLit = class extends LitElement23 {
10842
10713
  }
10843
10714
  connectedCallback() {
10844
10715
  super.connectedCallback();
10845
- ensureStyles5();
10716
+ ensureStylesInRoot(this, "syntro-pdp-regional-fullbleed-style", CSS5);
10846
10717
  }
10847
10718
  render() {
10848
- if (!this.data) return html24`<div class="pdp-reg-fb" style="opacity:0.5;">…</div>`;
10719
+ if (!this.data) return html25`<div class="pdp-reg-fb" style="opacity:0.5;">…</div>`;
10849
10720
  const d = this.data;
10850
- return html24`
10721
+ return html25`
10851
10722
  <section class="pdp-reg-fb" aria-label="Where you are">
10852
10723
  <article class="pdp-reg-fb__panel">
10853
10724
  <svg class="pdp-reg-fb__glyph" viewBox="0 0 56 56" aria-hidden="true">
@@ -10890,15 +10761,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-reg
10890
10761
  }
10891
10762
 
10892
10763
  // 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
- }
10764
+ import { html as html26, LitElement as LitElement25 } from "lit";
10902
10765
  var CSS6 = `
10903
10766
  .pdp-reg-in {
10904
10767
  --r-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -10951,7 +10814,7 @@ var CSS6 = `
10951
10814
  font-style: italic;
10952
10815
  }
10953
10816
  `;
10954
- var PdpRegionalInlineNoteLit = class extends LitElement24 {
10817
+ var PdpRegionalInlineNoteLit = class extends LitElement25 {
10955
10818
  constructor() {
10956
10819
  super(...arguments);
10957
10820
  this.data = void 0;
@@ -10961,12 +10824,12 @@ var PdpRegionalInlineNoteLit = class extends LitElement24 {
10961
10824
  }
10962
10825
  connectedCallback() {
10963
10826
  super.connectedCallback();
10964
- ensureStyles6();
10827
+ ensureStylesInRoot(this, "syntro-pdp-regional-inline-style", CSS6);
10965
10828
  }
10966
10829
  render() {
10967
- if (!this.data) return html25`<div class="pdp-reg-in" style="opacity:0.5;">…</div>`;
10830
+ if (!this.data) return html26`<div class="pdp-reg-in" style="opacity:0.5;">…</div>`;
10968
10831
  const d = this.data;
10969
- return html25`
10832
+ return html26`
10970
10833
  <aside class="pdp-reg-in" aria-label="Regional note">
10971
10834
  <svg class="pdp-reg-in__icon" viewBox="0 0 22 22" aria-hidden="true">
10972
10835
  <circle cx="11" cy="11" r="9" fill="none" stroke="currentColor" stroke-width="1.6"/>
@@ -10994,8 +10857,8 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-reg
10994
10857
  }
10995
10858
 
10996
10859
  // src/widgets/PdpSlotPlaceholderLit.ts
10997
- import { html as html26, LitElement as LitElement25 } from "lit";
10998
- var PdpSlotPlaceholderLit = class extends LitElement25 {
10860
+ import { html as html27, LitElement as LitElement26 } from "lit";
10861
+ var PdpSlotPlaceholderLit = class extends LitElement26 {
10999
10862
  constructor() {
11000
10863
  super(...arguments);
11001
10864
  this.label = "More personalized content coming soon";
@@ -11004,7 +10867,7 @@ var PdpSlotPlaceholderLit = class extends LitElement25 {
11004
10867
  return this;
11005
10868
  }
11006
10869
  render() {
11007
- return html26`
10870
+ return html27`
11008
10871
  <div class="pdp-slot-placeholder" data-placeholder>
11009
10872
  <p>${this.label}</p>
11010
10873
  <small>Ask in chat to explore this product further.</small>
@@ -11020,15 +10883,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-slo
11020
10883
  }
11021
10884
 
11022
10885
  // 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
- }
10886
+ import { html as html28, LitElement as LitElement27, nothing as nothing11 } from "lit";
11032
10887
  var RIBBON_CSS = `
11033
10888
  .syntro-pdp-ribbon {
11034
10889
  --rb-primary: var(--syntro-pdp-primary, hsl(150 32% 38%));
@@ -11087,7 +10942,7 @@ var RIBBON_CSS = `
11087
10942
  width: 16px;
11088
10943
  height: 16px;
11089
10944
  display: inline-block;
11090
- color: var(--rb-primary);
10945
+ color: var(--syntro-pdp-primary-ink, var(--rb-primary));
11091
10946
  }
11092
10947
  .syntro-pdp-ribbon-copy {
11093
10948
  font-family: var(--rb-display);
@@ -11104,7 +10959,7 @@ var RIBBON_CSS = `
11104
10959
  font-style: normal;
11105
10960
  font-family: var(--syntro-pdp-sans, inherit);
11106
10961
  font-weight: 500;
11107
- color: var(--rb-primary);
10962
+ color: var(--syntro-pdp-primary-ink, var(--rb-primary));
11108
10963
  font-size: 14.5px;
11109
10964
  letter-spacing: 0;
11110
10965
  }
@@ -11149,7 +11004,7 @@ var RIBBON_CSS = `
11149
11004
  100% { transform: translateX(110%); }
11150
11005
  }
11151
11006
  `;
11152
- var PdpRibbonLit = class extends LitElement26 {
11007
+ var PdpRibbonLit = class extends LitElement27 {
11153
11008
  constructor() {
11154
11009
  super(...arguments);
11155
11010
  this.topic = "";
@@ -11163,7 +11018,7 @@ var PdpRibbonLit = class extends LitElement26 {
11163
11018
  }
11164
11019
  connectedCallback() {
11165
11020
  super.connectedCallback();
11166
- ensureRibbonStyles();
11021
+ ensureStylesInRoot(this, "syntro-pdp-ribbon-style", RIBBON_CSS);
11167
11022
  if (this._entering) this.requestUpdate();
11168
11023
  }
11169
11024
  disconnectedCallback() {
@@ -11187,7 +11042,7 @@ var PdpRibbonLit = class extends LitElement26 {
11187
11042
  }
11188
11043
  render() {
11189
11044
  const cls = this._entering ? "syntro-pdp-ribbon is-entering" : "syntro-pdp-ribbon";
11190
- return html27`
11045
+ return html28`
11191
11046
  <div class="${cls}" role="status" aria-live="polite">
11192
11047
  <span class="syntro-pdp-ribbon-sweep" aria-hidden="true"></span>
11193
11048
  <svg
@@ -11207,7 +11062,7 @@ var PdpRibbonLit = class extends LitElement26 {
11207
11062
  </svg>
11208
11063
  <p class="syntro-pdp-ribbon-copy">
11209
11064
  Tailored to your conversation about
11210
- ${this.topic ? html27` <span class="syntro-pdp-ribbon-topic">${this.topic}</span>.` : nothing10}
11065
+ ${this.topic ? html28` <span class="syntro-pdp-ribbon-topic">${this.topic}</span>.` : nothing11}
11211
11066
  <span class="syntro-pdp-ribbon-trail"> ${this.subtitle}</span>
11212
11067
  </p>
11213
11068
  </div>
@@ -11224,15 +11079,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-rib
11224
11079
  }
11225
11080
 
11226
11081
  // 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
- }
11082
+ import { html as html29, LitElement as LitElement28, nothing as nothing12 } from "lit";
11236
11083
  var QA_CSS = `
11237
11084
  .pdp-qa {
11238
11085
  --qa-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -11314,7 +11161,7 @@ var QA_CSS = `
11314
11161
  background: color-mix(in srgb, var(--qa-primary) 14%, transparent);
11315
11162
  display: grid;
11316
11163
  place-items: center;
11317
- color: color-mix(in srgb, var(--qa-primary) 80%, white 20%);
11164
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--qa-primary) 80%, white 20%));
11318
11165
  transition: background 200ms ease, transform 200ms ease;
11319
11166
  }
11320
11167
  .pdp-qa-item[open] .pdp-qa-toggle {
@@ -11386,7 +11233,7 @@ var QA_CSS = `
11386
11233
  }
11387
11234
  }
11388
11235
  `;
11389
- var PdpQaLit = class extends LitElement27 {
11236
+ var PdpQaLit = class extends LitElement28 {
11390
11237
  constructor() {
11391
11238
  super(...arguments);
11392
11239
  this.data = void 0;
@@ -11398,7 +11245,7 @@ var PdpQaLit = class extends LitElement27 {
11398
11245
  }
11399
11246
  connectedCallback() {
11400
11247
  super.connectedCallback();
11401
- ensureQaStyles();
11248
+ ensureStylesInRoot(this, "syntro-pdp-qa-style", QA_CSS);
11402
11249
  if (this._entering) this.requestUpdate();
11403
11250
  }
11404
11251
  disconnectedCallback() {
@@ -11421,19 +11268,19 @@ var PdpQaLit = class extends LitElement27 {
11421
11268
  }
11422
11269
  render() {
11423
11270
  if (!this.data) {
11424
- return html28`<div data-skeleton class="pdp-qa-skeleton">
11271
+ return html29`<div data-skeleton class="pdp-qa-skeleton">
11425
11272
  <div class="pdp-qa-skeleton-row"></div>
11426
11273
  <div class="pdp-qa-skeleton-row"></div>
11427
11274
  <div class="pdp-qa-skeleton-row"></div>
11428
11275
  </div>`;
11429
11276
  }
11430
- return html28`
11277
+ return html29`
11431
11278
  <section
11432
11279
  class="${this._entering ? "pdp-qa is-entering" : "pdp-qa"}"
11433
11280
  aria-label="Frequently asked questions"
11434
11281
  >
11435
11282
  ${this.data.items.map(
11436
- (item, idx) => html28`
11283
+ (item, idx) => html29`
11437
11284
  <details class="pdp-qa-item" ?open=${idx === 0}>
11438
11285
  <summary class="pdp-qa-question">
11439
11286
  <span>${item.question}</span>
@@ -11448,7 +11295,7 @@ var PdpQaLit = class extends LitElement27 {
11448
11295
  </details>
11449
11296
  `
11450
11297
  )}
11451
- ${nothing11}
11298
+ ${nothing12}
11452
11299
  </section>
11453
11300
  `;
11454
11301
  }
@@ -11462,15 +11309,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-qa"
11462
11309
  }
11463
11310
 
11464
11311
  // 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
- }
11312
+ import { html as html30, LitElement as LitElement29 } from "lit";
11474
11313
  var REGIONAL_CSS = `
11475
11314
  .pdp-regional {
11476
11315
  --rg-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -11548,7 +11387,7 @@ var REGIONAL_CSS = `
11548
11387
  font-size: 11px;
11549
11388
  letter-spacing: 0.12em;
11550
11389
  text-transform: uppercase;
11551
- color: color-mix(in srgb, var(--rg-primary) 75%, white 25%);
11390
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--rg-primary) 75%, white 25%));
11552
11391
  font-weight: 500;
11553
11392
  margin-bottom: 22px;
11554
11393
  }
@@ -11580,7 +11419,7 @@ var REGIONAL_CSS = `
11580
11419
  .pdp-regional-headline em {
11581
11420
  font-style: italic;
11582
11421
  font-variation-settings: 'SOFT' 100, 'WONK' 1;
11583
- color: var(--rg-primary);
11422
+ color: var(--syntro-pdp-primary-ink, var(--rg-primary));
11584
11423
  }
11585
11424
 
11586
11425
  .pdp-regional-body {
@@ -11604,7 +11443,7 @@ var REGIONAL_CSS = `
11604
11443
  font-family: var(--rg-display);
11605
11444
  font-size: 14.5px;
11606
11445
  font-variation-settings: 'SOFT' 70;
11607
- color: color-mix(in srgb, var(--rg-primary) 65%, white 35%);
11446
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--rg-primary) 65%, white 35%));
11608
11447
  line-height: 1.5;
11609
11448
  }
11610
11449
  .pdp-regional-footer-spark {
@@ -11650,7 +11489,7 @@ var REGIONAL_CSS = `
11650
11489
  to { opacity: 1; transform: translateY(0); }
11651
11490
  }
11652
11491
  `;
11653
- var PdpRegionalLit = class extends LitElement28 {
11492
+ var PdpRegionalLit = class extends LitElement29 {
11654
11493
  constructor() {
11655
11494
  super(...arguments);
11656
11495
  this.data = void 0;
@@ -11662,7 +11501,7 @@ var PdpRegionalLit = class extends LitElement28 {
11662
11501
  }
11663
11502
  connectedCallback() {
11664
11503
  super.connectedCallback();
11665
- ensureRegionalStyles();
11504
+ ensureStylesInRoot(this, "syntro-pdp-regional-style", REGIONAL_CSS);
11666
11505
  if (this._entering) this.requestUpdate();
11667
11506
  }
11668
11507
  disconnectedCallback() {
@@ -11686,11 +11525,11 @@ var PdpRegionalLit = class extends LitElement28 {
11686
11525
  }
11687
11526
  render() {
11688
11527
  if (!this.data) {
11689
- return html29`<div data-skeleton class="pdp-qa-skeleton" style="height:200px;">
11528
+ return html30`<div data-skeleton class="pdp-qa-skeleton" style="height:200px;">
11690
11529
  <div class="pdp-qa-skeleton-row" style="height:200px;border-radius:24px;"></div>
11691
11530
  </div>`;
11692
11531
  }
11693
- return html29`
11532
+ return html30`
11694
11533
  <section
11695
11534
  class="${this._entering ? "pdp-regional is-entering" : "pdp-regional"}"
11696
11535
  aria-label="Regional and temporal context"
@@ -11735,15 +11574,7 @@ if (typeof customElements !== "undefined" && !customElements.get("syntro-pdp-reg
11735
11574
  }
11736
11575
 
11737
11576
  // 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
- }
11577
+ import { html as html31, LitElement as LitElement30 } from "lit";
11747
11578
  var PEER_FEED_CSS = `
11748
11579
  .pdp-peer-feed {
11749
11580
  --pf-fg: var(--syntro-pdp-fg, hsl(35 15% 92%));
@@ -11859,14 +11690,14 @@ var PEER_FEED_CSS = `
11859
11690
  }
11860
11691
  .pdp-peer-quote::before {
11861
11692
  content: '\u201C ';
11862
- color: color-mix(in srgb, var(--pf-primary) 60%, transparent);
11693
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pf-primary) 60%, transparent));
11863
11694
  font-size: 1.4em;
11864
11695
  vertical-align: -0.2em;
11865
11696
  margin-right: -2px;
11866
11697
  }
11867
11698
  .pdp-peer-quote::after {
11868
11699
  content: ' \u201D';
11869
- color: color-mix(in srgb, var(--pf-primary) 60%, transparent);
11700
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pf-primary) 60%, transparent));
11870
11701
  font-size: 1.4em;
11871
11702
  vertical-align: -0.2em;
11872
11703
  margin-left: -2px;
@@ -11877,7 +11708,7 @@ var PEER_FEED_CSS = `
11877
11708
  align-items: center;
11878
11709
  gap: 7px;
11879
11710
  font-size: 12.5px;
11880
- color: color-mix(in srgb, var(--pf-primary) 75%, white 25%);
11711
+ color: var(--syntro-pdp-primary-ink, color-mix(in srgb, var(--pf-primary) 75%, white 25%));
11881
11712
  font-style: italic;
11882
11713
  font-family: var(--pf-display);
11883
11714
  margin-top: 2px;
@@ -11947,7 +11778,7 @@ var PEER_FEED_CSS = `
11947
11778
  }
11948
11779
  }
11949
11780
  `;
11950
- var PdpPeerFeedLit = class extends LitElement29 {
11781
+ var PdpPeerFeedLit = class extends LitElement30 {
11951
11782
  constructor() {
11952
11783
  super(...arguments);
11953
11784
  this.data = void 0;
@@ -11959,7 +11790,7 @@ var PdpPeerFeedLit = class extends LitElement29 {
11959
11790
  }
11960
11791
  connectedCallback() {
11961
11792
  super.connectedCallback();
11962
- ensurePeerFeedStyles();
11793
+ ensureStylesInRoot(this, "syntro-pdp-peer-feed-style", PEER_FEED_CSS);
11963
11794
  if (this._entering) this.requestUpdate();
11964
11795
  }
11965
11796
  disconnectedCallback() {
@@ -11982,19 +11813,19 @@ var PdpPeerFeedLit = class extends LitElement29 {
11982
11813
  }
11983
11814
  render() {
11984
11815
  if (!this.data) {
11985
- return html30`<div data-skeleton class="pdp-peer-skeleton">
11816
+ return html31`<div data-skeleton class="pdp-peer-skeleton">
11986
11817
  <div class="pdp-peer-skeleton-row"></div>
11987
11818
  <div class="pdp-peer-skeleton-row"></div>
11988
11819
  <div class="pdp-peer-skeleton-row"></div>
11989
11820
  </div>`;
11990
11821
  }
11991
- return html30`
11822
+ return html31`
11992
11823
  <section
11993
11824
  class="${this._entering ? "pdp-peer-feed is-entering" : "pdp-peer-feed"}"
11994
11825
  aria-label="Users like you"
11995
11826
  >
11996
11827
  ${this.data.peers.map(
11997
- (peer) => html30`
11828
+ (peer) => html31`
11998
11829
  <article class="pdp-peer-post">
11999
11830
  <div class="pdp-peer-avatar" aria-hidden="true">${peer.initials}</div>
12000
11831
  <div class="pdp-peer-body">
@@ -12031,7 +11862,8 @@ var TAGS = {
12031
11862
  hero: "syntro-product-hero",
12032
11863
  grid: "syntro-product-grid",
12033
11864
  pdp: "syntro-pdp",
12034
- reco: "syntro-product-reco-carousel"
11865
+ reco: "syntro-product-reco-carousel",
11866
+ details: "syntro-product-details"
12035
11867
  };
12036
11868
  if (typeof customElements !== "undefined") {
12037
11869
  if (!customElements.get(TAGS.card)) customElements.define(TAGS.card, ProductCardLit);
@@ -12041,6 +11873,7 @@ if (typeof customElements !== "undefined") {
12041
11873
  if (!customElements.get(TAGS.grid)) customElements.define(TAGS.grid, ProductGridLit);
12042
11874
  if (!customElements.get(TAGS.pdp)) customElements.define(TAGS.pdp, SyntroPdpLit);
12043
11875
  if (!customElements.get(TAGS.reco)) customElements.define(TAGS.reco, ProductRecoCarouselLit);
11876
+ if (!customElements.get(TAGS.details)) customElements.define(TAGS.details, ProductDetailsLit);
12044
11877
  }
12045
11878
  if (typeof document !== "undefined") startNativeSlotWatcher();
12046
11879
  var _WIRED_RUNTIMES = /* @__PURE__ */ new WeakSet();
@@ -12081,17 +11914,21 @@ function onActivate(runtime2) {
12081
11914
  const observer2 = wirePdpElements(runtime2, document);
12082
11915
  return () => {
12083
11916
  observer2.disconnect();
12084
- const i2 = _ACTIVE_OBSERVERS.indexOf(observer2);
12085
- if (i2 >= 0) _ACTIVE_OBSERVERS.splice(i2, 1);
11917
+ const i = _ACTIVE_OBSERVERS.indexOf(observer2);
11918
+ if (i >= 0) _ACTIVE_OBSERVERS.splice(i, 1);
12086
11919
  _WIRED_RUNTIMES.delete(runtime2);
12087
11920
  };
12088
11921
  }
12089
- function makeMountable(tag) {
11922
+ function makeMountable(tag, hooks) {
12090
11923
  return {
12091
11924
  mount(container, config) {
12092
- const widgetProps = stripMountPlumbing(config ?? null);
11925
+ const widgetProps = resolveStorefrontProps(
11926
+ stripMountPlumbing(config ?? null),
11927
+ config?.runtime
11928
+ );
12093
11929
  const el = document.createElement(tag);
12094
11930
  el.props = widgetProps;
11931
+ hooks?.afterCreate?.(el, config);
12095
11932
  container.appendChild(el);
12096
11933
  return () => el.remove();
12097
11934
  }
@@ -12101,8 +11938,13 @@ var ProductCardMountable = makeMountable(TAGS.card);
12101
11938
  var ProductComparisonMountable = makeMountable(TAGS.comparison);
12102
11939
  var ProductHeroMountable = makeMountable(TAGS.hero);
12103
11940
  var ProductGridMountable = makeMountable(TAGS.grid);
12104
- var SyntroPdpMountable = makeMountable(TAGS.pdp);
11941
+ var SyntroPdpMountable = makeMountable(TAGS.pdp, {
11942
+ afterCreate: (el, config) => {
11943
+ el.runtimeRef = config?.runtime ?? null;
11944
+ }
11945
+ });
12105
11946
  var SyntroRecoMountable = makeMountable(TAGS.reco);
11947
+ var ProductDetailsMountable = makeMountable(TAGS.details);
12106
11948
  var runtime = {
12107
11949
  id: "adaptive-product",
12108
11950
  version: "1.0.0",
@@ -12185,6 +12027,15 @@ var runtime = {
12185
12027
  description: "Horizontally-scrollable carousel of recommended products with image, price, hook copy, and PDP links.",
12186
12028
  icon: "\u{1F3A0}"
12187
12029
  }
12030
+ },
12031
+ {
12032
+ id: "adaptive-product:details",
12033
+ component: ProductDetailsMountable,
12034
+ metadata: {
12035
+ name: "Product details",
12036
+ description: "The store's own product details",
12037
+ icon: "\u{1F4CB}"
12038
+ }
12188
12039
  }
12189
12040
  ]
12190
12041
  };
@@ -12201,35 +12052,8 @@ export {
12201
12052
  ProductGridMountable,
12202
12053
  SyntroPdpMountable,
12203
12054
  SyntroRecoMountable,
12055
+ ProductDetailsMountable,
12204
12056
  runtime,
12205
12057
  runtime_default
12206
12058
  };
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
12059
+ //# sourceMappingURL=chunk-YEW72FVW.js.map