@guided-tour-s4marth/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/player.js ADDED
@@ -0,0 +1,270 @@
1
+ import { driver } from 'driver.js';
2
+ import { resolveAnchor, waitForAnchor } from './resolver.js';
3
+ import { decodeLocator, waitForLocator } from './locator.js';
4
+ import { emit } from './telemetry.js';
5
+ function waitMs(ms) {
6
+ return new Promise(r => setTimeout(r, ms));
7
+ }
8
+ async function runPrepare(step, anchorMeta, navigate, waitForElement) {
9
+ if (!step.prepare?.length)
10
+ return;
11
+ for (const action of step.prepare) {
12
+ if (action.action === 'navigate') {
13
+ if (navigate)
14
+ await navigate(action.route);
15
+ // Give React time to commit the new route before continuing
16
+ await waitMs(300);
17
+ }
18
+ else if (action.action === 'click') {
19
+ const el = resolveAnchor(action.anchorId, anchorMeta);
20
+ if (el)
21
+ el.click();
22
+ await waitMs(150);
23
+ }
24
+ else if (action.action === 'wait') {
25
+ if (action.anchorId && waitForElement) {
26
+ const selector = `[data-tour="${CSS.escape(action.anchorId)}"]`;
27
+ await waitForElement(selector, action.timeoutMs);
28
+ }
29
+ else {
30
+ await waitMs(action.timeoutMs ?? 300);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ // The full polished stylesheet is static and driven by CSS variables with good
36
+ // defaults; a tour/provider theme only overrides the variables it sets. This is
37
+ // injected once (idempotent) so even an un-themed tour looks modern out of the box.
38
+ // Note: backdrop-filter: blur() is intentionally excluded — it blurs the cutout too.
39
+ const TOUR_STYLESHEET = `
40
+ .driver-popover {
41
+ background: var(--tour-bg, #ffffff);
42
+ color: var(--tour-text, #0f172a);
43
+ font-family: var(--tour-font, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
44
+ border-radius: var(--tour-radius, 14px);
45
+ border: 1px solid var(--tour-border, rgba(15,23,42,0.08));
46
+ box-shadow: var(--tour-shadow, 0 16px 40px rgba(2,6,23,0.28), 0 2px 8px rgba(2,6,23,0.12));
47
+ padding: 18px 18px 14px;
48
+ max-width: 340px;
49
+ animation: tour-pop-in 0.24s cubic-bezier(0.16, 1, 0.3, 1) both;
50
+ }
51
+ .driver-popover-title {
52
+ font-size: 15px; font-weight: 650; line-height: 1.35;
53
+ color: var(--tour-text, #0f172a); margin: 0 0 6px;
54
+ }
55
+ .driver-popover-description {
56
+ font-size: 13px; line-height: 1.55;
57
+ color: var(--tour-muted, #64748b); margin: 0;
58
+ }
59
+ .driver-popover-progress-text {
60
+ font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
61
+ color: var(--tour-muted, #94a3b8);
62
+ }
63
+ .driver-popover-footer { margin-top: 16px; gap: 8px; }
64
+ .driver-popover-footer button {
65
+ font-family: inherit; font-size: 12.5px; font-weight: 600;
66
+ padding: 7px 14px; border-radius: calc(var(--tour-radius, 14px) * 0.55);
67
+ cursor: pointer; text-shadow: none;
68
+ transition: background 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease, transform 0.06s ease;
69
+ }
70
+ .driver-popover-footer button:active { transform: translateY(0.5px); }
71
+ .driver-popover-footer button:focus-visible {
72
+ outline: 2px solid var(--tour-primary, #6366f1); outline-offset: 2px;
73
+ }
74
+ .driver-popover-next-btn, .driver-popover-done-btn {
75
+ background: var(--tour-primary, #6366f1);
76
+ color: var(--tour-primary-text, #ffffff);
77
+ border: 1px solid var(--tour-primary, #6366f1);
78
+ }
79
+ .driver-popover-next-btn:hover, .driver-popover-done-btn:hover {
80
+ filter: brightness(1.08);
81
+ }
82
+ .driver-popover-prev-btn {
83
+ background: transparent; color: var(--tour-muted, #64748b);
84
+ border: 1px solid var(--tour-border, rgba(100,116,139,0.30));
85
+ }
86
+ .driver-popover-prev-btn:hover { background: var(--tour-hover, rgba(15,23,42,0.05)); }
87
+ .driver-popover-close-btn {
88
+ color: var(--tour-muted, #94a3b8);
89
+ transition: color 0.15s ease, transform 0.15s ease;
90
+ }
91
+ .driver-popover-close-btn:hover { color: var(--tour-text, #0f172a); transform: scale(1.12); }
92
+ .driver-popover-arrow-side-left.driver-popover-arrow { border-left-color: var(--tour-bg, #ffffff); }
93
+ .driver-popover-arrow-side-right.driver-popover-arrow { border-right-color: var(--tour-bg, #ffffff); }
94
+ .driver-popover-arrow-side-top.driver-popover-arrow { border-top-color: var(--tour-bg, #ffffff); }
95
+ .driver-popover-arrow-side-bottom.driver-popover-arrow { border-bottom-color: var(--tour-bg, #ffffff); }
96
+ @keyframes tour-pop-in { from { opacity: 0; transform: translateY(6px) scale(0.98); } to { opacity: 1; transform: none; } }
97
+ @media (prefers-reduced-motion: reduce) { .driver-popover { animation: none; } }
98
+ `;
99
+ function injectThemeStyle(theme) {
100
+ let el = document.getElementById('__tour-theme__');
101
+ if (!el) {
102
+ el = document.createElement('style');
103
+ el.id = '__tour-theme__';
104
+ document.head.appendChild(el);
105
+ }
106
+ // Only the variables the theme overrides are emitted; everything else uses the
107
+ // built-in defaults baked into TOUR_STYLESHEET via var() fallbacks.
108
+ const vars = [];
109
+ if (theme.popoverBg)
110
+ vars.push(`--tour-bg:${theme.popoverBg}`);
111
+ if (theme.textColor)
112
+ vars.push(`--tour-text:${theme.textColor}`);
113
+ if (theme.mutedColor)
114
+ vars.push(`--tour-muted:${theme.mutedColor}`);
115
+ if (theme.primaryColor)
116
+ vars.push(`--tour-primary:${theme.primaryColor}`);
117
+ if (theme.primaryTextColor)
118
+ vars.push(`--tour-primary-text:${theme.primaryTextColor}`);
119
+ if (theme.borderRadius)
120
+ vars.push(`--tour-radius:${theme.borderRadius}`);
121
+ if (theme.fontFamily)
122
+ vars.push(`--tour-font:${theme.fontFamily}`);
123
+ if (theme.shadow)
124
+ vars.push(`--tour-shadow:${theme.shadow}`);
125
+ el.textContent = `${vars.length ? `:root{${vars.join(';')}}` : ''}\n${TOUR_STYLESHEET}`;
126
+ }
127
+ function buildPopover(step, stepNumber, totalVisible, isLast) {
128
+ const side = step.placement === 'auto' ? undefined : step.placement;
129
+ return {
130
+ title: step.title,
131
+ description: step.body,
132
+ showButtons: ['next', 'close'],
133
+ showProgress: true,
134
+ progressText: `${stepNumber} of ${totalVisible}`,
135
+ ...(isLast ? { nextBtnText: 'Done' } : {}),
136
+ ...(side ? { side } : {}),
137
+ };
138
+ }
139
+ // ─── Main ─────────────────────────────────────────────────────────────────────
140
+ export async function playTour(opts) {
141
+ const { tour, anchorMeta = {}, theme, onComplete, onSkip, onStepChange, onUnavailable, navigate, waitForElement } = opts;
142
+ // Always inject — the stylesheet carries the polished defaults, and any theme
143
+ // (provider theme < tour.theme) just overrides the CSS variables it sets.
144
+ const mergedTheme = { ...theme, ...(tour.theme ?? {}) };
145
+ injectThemeStyle(mergedTheme);
146
+ emit({ type: 'tour.started', tourId: tour.id });
147
+ // Count displayable steps upfront for the progress indicator.
148
+ // Floating steps (no anchorId) always count; anchored steps count even if
149
+ // the element isn't in DOM yet — we'll wait for it below.
150
+ const totalVisible = tour.steps.length;
151
+ let stepNumber = 0;
152
+ let shownCount = 0; // steps actually displayed
153
+ const missingAnchors = []; // anchored steps whose target never resolved
154
+ // ── One persistent driver instance for the whole tour ──────────────────────
155
+ // Reusing it lets driver.js animate the spotlight gliding between elements (and
156
+ // smooth-scroll them into view) — the "shifting focus" feel — instead of the
157
+ // overlay disappearing and popping back for each step.
158
+ let currentResolve = null;
159
+ let torn = false;
160
+ const settle = (action) => {
161
+ const resolve = currentResolve;
162
+ currentResolve = null;
163
+ resolve?.(action);
164
+ };
165
+ const teardown = () => {
166
+ if (torn)
167
+ return;
168
+ torn = true;
169
+ driverObj.destroy();
170
+ };
171
+ const driverObj = driver({
172
+ allowClose: true,
173
+ overlayColor: mergedTheme.overlayColor ?? '#0b1220',
174
+ overlayOpacity: mergedTheme.overlayOpacity ?? 0.55,
175
+ stagePadding: mergedTheme.stagePadding ?? 8,
176
+ stageRadius: mergedTheme.stageRadius ?? 8,
177
+ animate: mergedTheme.animate ?? true,
178
+ smoothScroll: true,
179
+ ...(mergedTheme.popoverClass ? { popoverClass: mergedTheme.popoverClass } : {}),
180
+ // Manual mode (hooks defined): the buttons do only what these say, so we
181
+ // drive step transitions ourselves via highlight() — keeping one instance.
182
+ // Next does NOT destroy; we just resolve and the loop highlights the next
183
+ // step on the same instance (the animated move). Close/esc tear down.
184
+ onNextClick: () => settle('next'),
185
+ onCloseClick: () => settle('skip'),
186
+ onDestroyStarted: () => { settle('skip'); teardown(); },
187
+ });
188
+ const waitForAction = () => new Promise(resolve => { currentResolve = resolve; });
189
+ for (let i = 0; i < tour.steps.length; i++) {
190
+ const step = tour.steps[i];
191
+ if (!step)
192
+ continue;
193
+ // ── 1. Run this step's prepare path (navigate + click + wait) ──────────
194
+ // This runs just before the step is shown, NOT at tour start.
195
+ // Navigations actually fire here, in the right sequence.
196
+ await runPrepare(step, anchorMeta, navigate, waitForElement);
197
+ // ── 2. Wait for the anchor element to appear in the DOM ────────────────
198
+ // Only wait the full timeout when this step navigated (new route renders
199
+ // async); otherwise a short wait, so a missing anchor doesn't hang for 5s.
200
+ const locator = step.anchorId ? decodeLocator(step.anchorId) : null;
201
+ if (step.anchorId) {
202
+ const hadNav = step.prepare?.some(a => a.action === 'navigate') ?? false;
203
+ const timeout = hadNav ? 5000 : 1500;
204
+ if (locator) {
205
+ // Encoded multi-signal locator — wait for it to resolve (signals/heal).
206
+ await waitForLocator(locator, timeout);
207
+ }
208
+ else if (waitForElement) {
209
+ await waitForElement(`[data-tour="${CSS.escape(step.anchorId)}"]`, timeout);
210
+ }
211
+ else {
212
+ await waitForAnchor(step.anchorId, timeout);
213
+ }
214
+ }
215
+ // ── 3. Resolve element ────────────────────────────────────────────────
216
+ let element;
217
+ if (step.anchorId) {
218
+ const el = resolveAnchor(step.anchorId, anchorMeta, tour.id, i);
219
+ if (!el) {
220
+ // Target missing — skip this step but keep the tour going. Telemetry
221
+ // (anchor.broken) already emitted by resolveAnchor.
222
+ missingAnchors.push(step.anchorId);
223
+ continue;
224
+ }
225
+ element = el;
226
+ // No manual scroll/settle here — driver.js (smoothScroll) scrolls the
227
+ // element into view and animates the stage as part of highlight().
228
+ }
229
+ stepNumber++;
230
+ const isLast = i === tour.steps.length - 1;
231
+ // ── 4. Emit telemetry + notify step change ─────────────────────────────
232
+ onStepChange?.(i);
233
+ const viewedEvent = step.anchorId
234
+ ? { type: 'step.viewed', tourId: tour.id, stepIndex: i, anchorId: step.anchorId }
235
+ : { type: 'step.viewed', tourId: tour.id, stepIndex: i };
236
+ emit(viewedEvent);
237
+ // ── 5. Move the spotlight to this step (animated) and wait for action ──
238
+ driverObj.highlight({
239
+ ...(element ? { element } : {}),
240
+ popover: buildPopover(step, stepNumber, totalVisible, isLast),
241
+ });
242
+ shownCount++;
243
+ const action = await waitForAction();
244
+ if (action === 'skip') {
245
+ emit({ type: 'tour.skipped', tourId: tour.id, stepIndex: i });
246
+ teardown();
247
+ onSkip?.(i);
248
+ return;
249
+ }
250
+ if (isLast) {
251
+ emit({ type: 'tour.completed', tourId: tour.id });
252
+ teardown();
253
+ onComplete?.();
254
+ return;
255
+ }
256
+ // action === 'next' — loop continues; the next highlight() animates the move
257
+ }
258
+ // Loop finished without the user skipping/completing mid-way.
259
+ teardown();
260
+ if (shownCount === 0) {
261
+ // Every anchored step's target was missing — nothing was shown. Do NOT
262
+ // complete or mark seen; surface it so it can show once the UI is fixed.
263
+ emit({ type: 'tour.unavailable', tourId: tour.id, missingAnchors });
264
+ onUnavailable?.(missingAnchors);
265
+ return;
266
+ }
267
+ emit({ type: 'tour.completed', tourId: tour.id });
268
+ onComplete?.();
269
+ }
270
+ //# sourceMappingURL=player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface AnchorMeta {
2
+ testid?: string;
3
+ role?: string;
4
+ name?: string;
5
+ }
6
+ export type AnchorMetaMap = Record<string, AnchorMeta>;
7
+ /**
8
+ * Resolve a step's target from its `anchorId`.
9
+ *
10
+ * `anchorId` carries one of two things (no schema change — the field is reused):
11
+ * - an encoded multi-signal locator (`loc:<json>`) → resolved via signals +
12
+ * signature, with self-heal and rot detection (the modern path), or
13
+ * - a plain string → treated as a legacy `data-tour` selector.
14
+ *
15
+ * Emits telemetry: `anchor.healed` (recovered via signature), `anchor.mismatch`
16
+ * (resolved the wrong element — skipped), `anchor.broken` (nothing found).
17
+ */
18
+ export declare function resolveAnchor(anchorId: string, metaMap?: AnchorMetaMap, tourId?: string, stepIndex?: number): Element | null;
19
+ export declare function waitForAnchor(anchorId: string, timeoutMs?: number): Promise<Element | null>;
20
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,EAC3B,MAAM,SAAK,EACX,SAAS,SAAI,GACZ,OAAO,GAAG,IAAI,CA2ChB;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAsBzF"}
@@ -0,0 +1,81 @@
1
+ import { emit } from './telemetry.js';
2
+ import { decodeLocator, resolveLocator } from './locator.js';
3
+ /**
4
+ * Resolve a step's target from its `anchorId`.
5
+ *
6
+ * `anchorId` carries one of two things (no schema change — the field is reused):
7
+ * - an encoded multi-signal locator (`loc:<json>`) → resolved via signals +
8
+ * signature, with self-heal and rot detection (the modern path), or
9
+ * - a plain string → treated as a legacy `data-tour` selector.
10
+ *
11
+ * Emits telemetry: `anchor.healed` (recovered via signature), `anchor.mismatch`
12
+ * (resolved the wrong element — skipped), `anchor.broken` (nothing found).
13
+ */
14
+ export function resolveAnchor(anchorId, metaMap = {}, tourId = '', stepIndex = 0) {
15
+ // ── Modern path: encoded locator + signature ──────────────────────────────
16
+ const locator = decodeLocator(anchorId);
17
+ if (locator) {
18
+ const { el, status } = resolveLocator(locator);
19
+ if (status === 'healed')
20
+ emit({ type: 'anchor.healed', anchorId, tourId, stepIndex });
21
+ if (status === 'mismatch')
22
+ emit({ type: 'anchor.mismatch', anchorId, tourId, stepIndex });
23
+ if (!el) {
24
+ if (status !== 'mismatch')
25
+ emit({ type: 'anchor.broken', anchorId, tourId, stepIndex });
26
+ return null;
27
+ }
28
+ return el;
29
+ }
30
+ // ── Legacy path: plain data-tour selector (+ registry metadata fallbacks) ──
31
+ const escaped = CSS.escape(anchorId);
32
+ const primary = document.querySelector(`[data-tour="${escaped}"]`);
33
+ if (primary)
34
+ return primary;
35
+ const meta = metaMap[anchorId];
36
+ if (meta?.testid) {
37
+ const el = document.querySelector(`[data-testid="${CSS.escape(meta.testid)}"]`);
38
+ if (el) {
39
+ emit({ type: 'anchor.fallback', anchorId, strategy: 'testid' });
40
+ return el;
41
+ }
42
+ }
43
+ if (meta?.role && meta?.name) {
44
+ const candidates = document.querySelectorAll(`[role="${meta.role}"]`);
45
+ for (const el of Array.from(candidates)) {
46
+ const label = el.getAttribute('aria-label') ??
47
+ el.getAttribute('aria-labelledby') ??
48
+ el.textContent?.trim();
49
+ if (label === meta.name) {
50
+ emit({ type: 'anchor.fallback', anchorId, strategy: 'role' });
51
+ return el;
52
+ }
53
+ }
54
+ }
55
+ emit({ type: 'anchor.broken', anchorId, tourId, stepIndex });
56
+ return null;
57
+ }
58
+ export function waitForAnchor(anchorId, timeoutMs = 5000) {
59
+ return new Promise(resolve => {
60
+ const escaped = CSS.escape(anchorId);
61
+ const existing = document.querySelector(`[data-tour="${escaped}"]`);
62
+ if (existing) {
63
+ resolve(existing);
64
+ return;
65
+ }
66
+ const observer = new MutationObserver(() => {
67
+ const el = document.querySelector(`[data-tour="${escaped}"]`);
68
+ if (el) {
69
+ observer.disconnect();
70
+ clearTimeout(timer);
71
+ resolve(el);
72
+ }
73
+ });
74
+ observer.observe(document.body, { childList: true, subtree: true, attributes: true });
75
+ const timer = setTimeout(() => {
76
+ observer.disconnect();
77
+ resolve(null);
78
+ }, timeoutMs);
79
+ });
80
+ }
81
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAU7D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,UAAyB,EAAE,EAC3B,MAAM,GAAG,EAAE,EACX,SAAS,GAAG,CAAC;IAEb,6EAA6E;IAC7E,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACxC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,MAAM,KAAK,QAAQ;YAAE,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACtF,IAAI,MAAM,KAAK,UAAU;YAAE,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1F,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,IAAI,MAAM,KAAK,UAAU;gBAAE,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACxF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,8EAA8E;IAC9E,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,OAAO,IAAI,CAAC,CAAC;IACnE,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAE5B,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,iBAAiB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChF,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;YAChE,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IACD,IAAI,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,QAAQ,CAAC,gBAAgB,CAAC,UAAU,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QACtE,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,MAAM,KAAK,GACT,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC;gBAC7B,EAAE,CAAC,YAAY,CAAC,iBAAiB,CAAC;gBAClC,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;YACzB,IAAI,KAAK,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;gBACxB,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC9D,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAC7D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,SAAS,GAAG,IAAI;IAC9D,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,OAAO,IAAI,CAAC,CAAC;QACpE,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,CAAC,QAAQ,CAAC,CAAC;YAClB,OAAO;QACT,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE;YACzC,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,OAAO,IAAI,CAAC,CAAC;YAC9D,IAAI,EAAE,EAAE,CAAC;gBACP,QAAQ,CAAC,UAAU,EAAE,CAAC;gBACtB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QACtF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,QAAQ,CAAC,UAAU,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC"}