easter-egg-quest 1.0.16 → 1.0.17
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.
|
@@ -520,7 +520,25 @@ function isElementVisible(el) {
|
|
|
520
520
|
if (rect.width === 0 || rect.height === 0) return false;
|
|
521
521
|
const style = getComputedStyle(el);
|
|
522
522
|
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
|
|
523
|
-
|
|
523
|
+
const inViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
|
524
|
+
if (!inViewport) return false;
|
|
525
|
+
let parent = el.parentElement;
|
|
526
|
+
while (parent && parent !== document.body) {
|
|
527
|
+
const parentStyle = getComputedStyle(parent);
|
|
528
|
+
const overflowClips = [
|
|
529
|
+
parentStyle.overflow,
|
|
530
|
+
parentStyle.overflowX,
|
|
531
|
+
parentStyle.overflowY
|
|
532
|
+
].some((value) => ["auto", "scroll", "hidden", "clip"].includes(value));
|
|
533
|
+
if (overflowClips) {
|
|
534
|
+
const parentRect = parent.getBoundingClientRect();
|
|
535
|
+
if (rect.bottom <= parentRect.top || rect.top >= parentRect.bottom || rect.right <= parentRect.left || rect.left >= parentRect.right) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
parent = parent.parentElement;
|
|
540
|
+
}
|
|
541
|
+
return true;
|
|
524
542
|
}
|
|
525
543
|
function isDangerousElement(el) {
|
|
526
544
|
const text = (el.textContent ?? "").toLowerCase().trim();
|
|
@@ -528,26 +546,6 @@ function isDangerousElement(el) {
|
|
|
528
546
|
const combined = `${text} ${ariaLabel}`;
|
|
529
547
|
return DANGEROUS_KEYWORDS.some((kw) => combined.includes(kw));
|
|
530
548
|
}
|
|
531
|
-
function findEligibleEntryElements(containerSelector, excludeSelectors = []) {
|
|
532
|
-
const container = containerSelector ? document.querySelector(containerSelector) ?? document.body : document.body;
|
|
533
|
-
const candidates = container.querySelectorAll(
|
|
534
|
-
'a, button, [role="button"], nav a, nav button, .cta, [data-easter-entry]'
|
|
535
|
-
);
|
|
536
|
-
const excludeSet = /* @__PURE__ */ new Set();
|
|
537
|
-
for (const sel of excludeSelectors) {
|
|
538
|
-
document.querySelectorAll(sel).forEach((el) => excludeSet.add(el));
|
|
539
|
-
}
|
|
540
|
-
const eligible = [];
|
|
541
|
-
candidates.forEach((el) => {
|
|
542
|
-
if (excludeSet.has(el)) return;
|
|
543
|
-
if (!isElementVisible(el)) return;
|
|
544
|
-
if (isDangerousElement(el)) return;
|
|
545
|
-
const rect = el.getBoundingClientRect();
|
|
546
|
-
if (rect.width < 30 || rect.height < 20) return;
|
|
547
|
-
eligible.push(el);
|
|
548
|
-
});
|
|
549
|
-
return eligible;
|
|
550
|
-
}
|
|
551
549
|
function pickRandom(arr) {
|
|
552
550
|
if (arr.length === 0) return void 0;
|
|
553
551
|
return arr[Math.floor(Math.random() * arr.length)];
|
|
@@ -577,6 +575,11 @@ class HiddenEntry {
|
|
|
577
575
|
this._intersectionObserver = null;
|
|
578
576
|
this._mutationDebounce = null;
|
|
579
577
|
this._navigationHandler = null;
|
|
578
|
+
this._bootstrapObserver = null;
|
|
579
|
+
this._retryTimer = null;
|
|
580
|
+
this._fallbackTimer = null;
|
|
581
|
+
this._retryCount = 0;
|
|
582
|
+
this._historyPatched = false;
|
|
580
583
|
this.config = config;
|
|
581
584
|
this.script = script;
|
|
582
585
|
this.onFound = onFound;
|
|
@@ -586,16 +589,10 @@ class HiddenEntry {
|
|
|
586
589
|
this._createFallbackEntry();
|
|
587
590
|
return;
|
|
588
591
|
}
|
|
589
|
-
const eligible = findEligibleEntryElements(
|
|
590
|
-
this.config.hiddenEntry.selector,
|
|
591
|
-
this.config.hiddenEntry.excludeSelectors
|
|
592
|
-
);
|
|
593
|
-
if (eligible.length === 0) {
|
|
594
|
-
this._createFallbackEntry();
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
this._injectIntoExisting(eligible);
|
|
598
592
|
this._startHintEscalation();
|
|
593
|
+
this._startBootstrapWatch();
|
|
594
|
+
this._scheduleFallback();
|
|
595
|
+
this._attemptInjection(0);
|
|
599
596
|
}
|
|
600
597
|
cleanup() {
|
|
601
598
|
var _a2, _b2, _c, _d;
|
|
@@ -611,6 +608,7 @@ class HiddenEntry {
|
|
|
611
608
|
this.targetElement.classList.remove("eeq-entry-target");
|
|
612
609
|
}
|
|
613
610
|
this._stopWatchdog();
|
|
611
|
+
this._stopBootstrapWatch();
|
|
614
612
|
(_a2 = this._injectedElement) == null ? void 0 : _a2.remove();
|
|
615
613
|
this._injectedElement = null;
|
|
616
614
|
(_b2 = this.hintContainer) == null ? void 0 : _b2.remove();
|
|
@@ -623,17 +621,11 @@ class HiddenEntry {
|
|
|
623
621
|
this._attachToElement(this.targetElement);
|
|
624
622
|
}
|
|
625
623
|
// ─── Inject trigger text inside an existing element ─────────────────
|
|
626
|
-
_injectIntoExisting(
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
).filter((el) => {
|
|
632
|
-
var _a2;
|
|
633
|
-
const text = ((_a2 = el.textContent) == null ? void 0 : _a2.trim()) ?? "";
|
|
634
|
-
if (text.length < 2 || el.closest("[data-eeq]")) return false;
|
|
635
|
-
return isElementVisible(el);
|
|
636
|
-
});
|
|
624
|
+
_injectIntoExisting() {
|
|
625
|
+
var _a2;
|
|
626
|
+
const root = this._getSearchRoot();
|
|
627
|
+
const candidates = this._findTextCandidates(root);
|
|
628
|
+
if (candidates.length === 0) return false;
|
|
637
629
|
const sorted = candidates.sort((a, b) => {
|
|
638
630
|
const aNav = a.closest("nav") !== null || a.closest("header") !== null ? 1 : 0;
|
|
639
631
|
const bNav = b.closest("nav") !== null || b.closest("header") !== null ? 1 : 0;
|
|
@@ -644,8 +636,8 @@ class HiddenEntry {
|
|
|
644
636
|
});
|
|
645
637
|
const prints = sorted.map(
|
|
646
638
|
(el) => {
|
|
647
|
-
var
|
|
648
|
-
return `${el.tagName}:${(((
|
|
639
|
+
var _a3;
|
|
640
|
+
return `${el.tagName}:${(((_a3 = el.textContent) == null ? void 0 : _a3.trim()) ?? "").slice(0, 30)}`;
|
|
649
641
|
}
|
|
650
642
|
);
|
|
651
643
|
const HISTORY_KEY = "eeq_trigger_history";
|
|
@@ -677,8 +669,8 @@ class HiddenEntry {
|
|
|
677
669
|
];
|
|
678
670
|
const rotatedPrints = rotated.map(
|
|
679
671
|
(el) => {
|
|
680
|
-
var
|
|
681
|
-
return `${el.tagName}:${(((
|
|
672
|
+
var _a3;
|
|
673
|
+
return `${el.tagName}:${(((_a3 = el.textContent) == null ? void 0 : _a3.trim()) ?? "").slice(0, 30)}`;
|
|
682
674
|
}
|
|
683
675
|
);
|
|
684
676
|
const unused = rotated.filter((_, i) => !usedSet.has(rotatedPrints[i]));
|
|
@@ -692,6 +684,7 @@ class HiddenEntry {
|
|
|
692
684
|
const isInline = host.tagName === "A" || host.tagName === "BUTTON" || host.tagName === "LI" || host.closest("nav") !== null;
|
|
693
685
|
const span = document.createElement("span");
|
|
694
686
|
span.textContent = isInline ? " · start hunt" : " start hunt";
|
|
687
|
+
span.setAttribute("data-eeq", "trigger");
|
|
695
688
|
span.style.cssText = `
|
|
696
689
|
font: inherit;
|
|
697
690
|
color: inherit;
|
|
@@ -719,10 +712,161 @@ class HiddenEntry {
|
|
|
719
712
|
this._injectedElement = span;
|
|
720
713
|
this.targetElement = span;
|
|
721
714
|
this._attachToElement(span);
|
|
715
|
+
this._retryCount = 0;
|
|
716
|
+
this._clearRetryTimer();
|
|
717
|
+
this._clearFallbackTimer();
|
|
718
|
+
(_a2 = this.fallbackButton) == null ? void 0 : _a2.remove();
|
|
719
|
+
this.fallbackButton = null;
|
|
720
|
+
this._stopBootstrapWatch();
|
|
722
721
|
this._startWatchdog();
|
|
723
|
-
return;
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
_attemptInjection(delayMs) {
|
|
727
|
+
this._clearRetryTimer();
|
|
728
|
+
this._retryTimer = setTimeout(() => {
|
|
729
|
+
if (this._destroyed || this._injectedElement) return;
|
|
730
|
+
const ok = this._injectIntoExisting();
|
|
731
|
+
if (!ok) this._scheduleNextRetry();
|
|
732
|
+
}, delayMs);
|
|
733
|
+
}
|
|
734
|
+
_scheduleNextRetry() {
|
|
735
|
+
if (this._destroyed || this._injectedElement) return;
|
|
736
|
+
const delays = [300, 900, 1800, 3200, 5e3, 8e3];
|
|
737
|
+
const nextDelay = delays[Math.min(this._retryCount, delays.length - 1)];
|
|
738
|
+
this._retryCount += 1;
|
|
739
|
+
this._attemptInjection(nextDelay);
|
|
740
|
+
}
|
|
741
|
+
_scheduleFallback() {
|
|
742
|
+
if (this.fallbackButton || this._fallbackTimer) return;
|
|
743
|
+
this._fallbackTimer = setTimeout(() => {
|
|
744
|
+
if (this._destroyed || this._injectedElement || this.fallbackButton) return;
|
|
745
|
+
this._createFallbackEntry();
|
|
746
|
+
}, 1e4);
|
|
747
|
+
}
|
|
748
|
+
_clearRetryTimer() {
|
|
749
|
+
if (this._retryTimer) {
|
|
750
|
+
clearTimeout(this._retryTimer);
|
|
751
|
+
this._retryTimer = null;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
_clearFallbackTimer() {
|
|
755
|
+
if (this._fallbackTimer) {
|
|
756
|
+
clearTimeout(this._fallbackTimer);
|
|
757
|
+
this._fallbackTimer = null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
_getSearchRoot() {
|
|
761
|
+
if (!this.config.hiddenEntry.selector) return document.body;
|
|
762
|
+
return document.querySelector(this.config.hiddenEntry.selector) ?? document.body;
|
|
763
|
+
}
|
|
764
|
+
_findTextCandidates(root) {
|
|
765
|
+
var _a2;
|
|
766
|
+
const selector = [
|
|
767
|
+
"p",
|
|
768
|
+
"li",
|
|
769
|
+
"figcaption",
|
|
770
|
+
"blockquote",
|
|
771
|
+
"caption",
|
|
772
|
+
"label",
|
|
773
|
+
"td",
|
|
774
|
+
"th",
|
|
775
|
+
"h1",
|
|
776
|
+
"h2",
|
|
777
|
+
"h3",
|
|
778
|
+
"h4",
|
|
779
|
+
"h5",
|
|
780
|
+
"h6",
|
|
781
|
+
"a",
|
|
782
|
+
"button",
|
|
783
|
+
'[role="button"]',
|
|
784
|
+
'[role="link"]',
|
|
785
|
+
"div",
|
|
786
|
+
"span",
|
|
787
|
+
"article",
|
|
788
|
+
"section",
|
|
789
|
+
"summary",
|
|
790
|
+
"[data-easter-entry]",
|
|
791
|
+
"[mat-button]",
|
|
792
|
+
"[mat-list-item]",
|
|
793
|
+
".mat-mdc-button",
|
|
794
|
+
".mat-mdc-list-item",
|
|
795
|
+
".ag-cell",
|
|
796
|
+
".ag-header-cell"
|
|
797
|
+
].join(", ");
|
|
798
|
+
const excludeSet = /* @__PURE__ */ new Set();
|
|
799
|
+
for (const sel of this.config.hiddenEntry.excludeSelectors) {
|
|
800
|
+
(_a2 = root.querySelectorAll) == null ? void 0 : _a2.call(root, sel).forEach((el) => excludeSet.add(el));
|
|
801
|
+
}
|
|
802
|
+
return Array.from(root.querySelectorAll(selector)).filter((el) => {
|
|
803
|
+
var _a3, _b2;
|
|
804
|
+
if (!(el instanceof HTMLElement)) return false;
|
|
805
|
+
if (excludeSet.has(el)) return false;
|
|
806
|
+
if (el.closest("[data-eeq]")) return false;
|
|
807
|
+
if (el.closest("script, style, noscript, svg, canvas, form")) return false;
|
|
808
|
+
if (isDangerousElement(el)) return false;
|
|
809
|
+
if (!isElementVisible(el)) return false;
|
|
810
|
+
const text = ((_a3 = el.innerText) == null ? void 0 : _a3.trim()) || ((_b2 = el.textContent) == null ? void 0 : _b2.trim()) || "";
|
|
811
|
+
if (text.length < 6 || text.length > 220) return false;
|
|
812
|
+
const childElementCount = el.children.length;
|
|
813
|
+
if (childElementCount > 12) return false;
|
|
814
|
+
const rect = el.getBoundingClientRect();
|
|
815
|
+
if (rect.width < 18 || rect.height < 12) return false;
|
|
816
|
+
return true;
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
_startBootstrapWatch() {
|
|
820
|
+
this._stopBootstrapWatch();
|
|
821
|
+
const root = this._getSearchRoot();
|
|
822
|
+
const observerTarget = root instanceof HTMLElement ? root : document.body;
|
|
823
|
+
this._bootstrapObserver = new MutationObserver(() => {
|
|
824
|
+
if (this._destroyed || this._injectedElement) return;
|
|
825
|
+
if (this._mutationDebounce) clearTimeout(this._mutationDebounce);
|
|
826
|
+
this._mutationDebounce = setTimeout(() => {
|
|
827
|
+
if (this._destroyed || this._injectedElement) return;
|
|
828
|
+
this._attemptInjection(150);
|
|
829
|
+
}, 250);
|
|
830
|
+
});
|
|
831
|
+
this._bootstrapObserver.observe(observerTarget, { childList: true, subtree: true });
|
|
832
|
+
this._navigationHandler = () => {
|
|
833
|
+
if (this._destroyed || this._injectedElement) return;
|
|
834
|
+
this._attemptInjection(700);
|
|
835
|
+
};
|
|
836
|
+
window.addEventListener("popstate", this._navigationHandler);
|
|
837
|
+
window.addEventListener("hashchange", this._navigationHandler);
|
|
838
|
+
if (!this._historyPatched && typeof window.history !== "undefined") {
|
|
839
|
+
this._historyPatched = true;
|
|
840
|
+
const patchHistoryMethod = (method) => {
|
|
841
|
+
const original = window.history[method].bind(window.history);
|
|
842
|
+
window.history[method] = (...args) => {
|
|
843
|
+
const result = original(...args);
|
|
844
|
+
window.dispatchEvent(new Event("eeq:navigation"));
|
|
845
|
+
return result;
|
|
846
|
+
};
|
|
847
|
+
};
|
|
848
|
+
patchHistoryMethod("pushState");
|
|
849
|
+
patchHistoryMethod("replaceState");
|
|
850
|
+
}
|
|
851
|
+
window.addEventListener("eeq:navigation", this._navigationHandler);
|
|
852
|
+
}
|
|
853
|
+
_stopBootstrapWatch() {
|
|
854
|
+
if (this._bootstrapObserver) {
|
|
855
|
+
this._bootstrapObserver.disconnect();
|
|
856
|
+
this._bootstrapObserver = null;
|
|
857
|
+
}
|
|
858
|
+
this._clearRetryTimer();
|
|
859
|
+
this._clearFallbackTimer();
|
|
860
|
+
if (this._mutationDebounce) {
|
|
861
|
+
clearTimeout(this._mutationDebounce);
|
|
862
|
+
this._mutationDebounce = null;
|
|
863
|
+
}
|
|
864
|
+
if (this._navigationHandler) {
|
|
865
|
+
window.removeEventListener("popstate", this._navigationHandler);
|
|
866
|
+
window.removeEventListener("hashchange", this._navigationHandler);
|
|
867
|
+
window.removeEventListener("eeq:navigation", this._navigationHandler);
|
|
868
|
+
this._navigationHandler = null;
|
|
724
869
|
}
|
|
725
|
-
this._createFallbackEntry();
|
|
726
870
|
}
|
|
727
871
|
/**
|
|
728
872
|
* Watch for the injected trigger element being removed from the DOM
|
|
@@ -769,6 +913,7 @@ class HiddenEntry {
|
|
|
769
913
|
};
|
|
770
914
|
window.addEventListener("popstate", this._navigationHandler);
|
|
771
915
|
window.addEventListener("hashchange", this._navigationHandler);
|
|
916
|
+
window.addEventListener("eeq:navigation", this._navigationHandler);
|
|
772
917
|
}
|
|
773
918
|
/**
|
|
774
919
|
* Walk up the DOM to find a stable ancestor that won't be replaced
|
|
@@ -803,6 +948,7 @@ class HiddenEntry {
|
|
|
803
948
|
if (this._navigationHandler) {
|
|
804
949
|
window.removeEventListener("popstate", this._navigationHandler);
|
|
805
950
|
window.removeEventListener("hashchange", this._navigationHandler);
|
|
951
|
+
window.removeEventListener("eeq:navigation", this._navigationHandler);
|
|
806
952
|
this._navigationHandler = null;
|
|
807
953
|
}
|
|
808
954
|
}
|
|
@@ -817,13 +963,12 @@ class HiddenEntry {
|
|
|
817
963
|
}
|
|
818
964
|
this._injectedElement = null;
|
|
819
965
|
this.targetElement = null;
|
|
966
|
+
this._startBootstrapWatch();
|
|
967
|
+
this._scheduleFallback();
|
|
820
968
|
setTimeout(() => {
|
|
821
969
|
if (this._destroyed) return;
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
this.config.hiddenEntry.excludeSelectors
|
|
825
|
-
);
|
|
826
|
-
this._injectIntoExisting(eligible);
|
|
970
|
+
const ok = this._injectIntoExisting();
|
|
971
|
+
if (!ok) this._scheduleNextRetry();
|
|
827
972
|
}, 1200);
|
|
828
973
|
}
|
|
829
974
|
/**
|
|
@@ -832,12 +977,22 @@ class HiddenEntry {
|
|
|
832
977
|
* Returns true if inserted without layout shift.
|
|
833
978
|
*/
|
|
834
979
|
_tryInsertBetweenWords(host, span) {
|
|
835
|
-
var _a2;
|
|
836
980
|
const textNodes = [];
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
981
|
+
const walker = document.createTreeWalker(host, NodeFilter.SHOW_TEXT, {
|
|
982
|
+
acceptNode: (node) => {
|
|
983
|
+
var _a2;
|
|
984
|
+
const text2 = ((_a2 = node.textContent) == null ? void 0 : _a2.trim()) ?? "";
|
|
985
|
+
if (text2.length <= 5) return NodeFilter.FILTER_REJECT;
|
|
986
|
+
const parent = node.parentElement;
|
|
987
|
+
if (!parent) return NodeFilter.FILTER_REJECT;
|
|
988
|
+
if (parent.closest("[data-eeq], script, style, noscript")) return NodeFilter.FILTER_REJECT;
|
|
989
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
840
990
|
}
|
|
991
|
+
});
|
|
992
|
+
let current = walker.nextNode();
|
|
993
|
+
while (current) {
|
|
994
|
+
textNodes.push(current);
|
|
995
|
+
current = walker.nextNode();
|
|
841
996
|
}
|
|
842
997
|
if (!textNodes.length) return false;
|
|
843
998
|
const slotSeed = Math.floor(Date.now() / (1e3 * 60 * 2));
|
|
@@ -854,13 +1009,13 @@ class HiddenEntry {
|
|
|
854
1009
|
const splitAt = spacePositions[Math.min(idx, spacePositions.length - 1)];
|
|
855
1010
|
const before = text.slice(0, splitAt);
|
|
856
1011
|
const after = text.slice(splitAt);
|
|
857
|
-
const
|
|
1012
|
+
const rectBefore = host.getBoundingClientRect();
|
|
858
1013
|
const afterNode = document.createTextNode(after);
|
|
859
1014
|
textNode.textContent = before;
|
|
860
1015
|
host.insertBefore(afterNode, textNode.nextSibling);
|
|
861
1016
|
host.insertBefore(span, afterNode);
|
|
862
|
-
const
|
|
863
|
-
if (Math.abs(
|
|
1017
|
+
const rectAfter = host.getBoundingClientRect();
|
|
1018
|
+
if (Math.abs(rectAfter.height - rectBefore.height) > 8 || Math.abs(rectAfter.width - rectBefore.width) > 24) {
|
|
864
1019
|
host.removeChild(span);
|
|
865
1020
|
host.removeChild(afterNode);
|
|
866
1021
|
textNode.textContent = text;
|
|
@@ -1105,6 +1260,7 @@ class HiddenEntry {
|
|
|
1105
1260
|
document.head.appendChild(this.shimmerStyle);
|
|
1106
1261
|
}
|
|
1107
1262
|
_createFallbackEntry() {
|
|
1263
|
+
if (this.fallbackButton) return;
|
|
1108
1264
|
this.fallbackButton = document.createElement("button");
|
|
1109
1265
|
this.fallbackButton.textContent = "·";
|
|
1110
1266
|
this.fallbackButton.setAttribute("aria-label", "Hidden game entry");
|