easter-egg-quest 1.0.15 → 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.
|
@@ -462,6 +462,12 @@ class InputTracker {
|
|
|
462
462
|
this._phases = [];
|
|
463
463
|
this._currentPhaseType = "still";
|
|
464
464
|
this._currentPhaseStart = Date.now();
|
|
465
|
+
this._snapshot.isMoving = false;
|
|
466
|
+
this._snapshot.velocity = 0;
|
|
467
|
+
if (this._stillTimer) {
|
|
468
|
+
clearTimeout(this._stillTimer);
|
|
469
|
+
this._stillTimer = null;
|
|
470
|
+
}
|
|
465
471
|
}
|
|
466
472
|
resetCounts() {
|
|
467
473
|
this._snapshot.totalClicks = 0;
|
|
@@ -514,7 +520,25 @@ function isElementVisible(el) {
|
|
|
514
520
|
if (rect.width === 0 || rect.height === 0) return false;
|
|
515
521
|
const style = getComputedStyle(el);
|
|
516
522
|
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
|
|
517
|
-
|
|
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;
|
|
518
542
|
}
|
|
519
543
|
function isDangerousElement(el) {
|
|
520
544
|
const text = (el.textContent ?? "").toLowerCase().trim();
|
|
@@ -522,26 +546,6 @@ function isDangerousElement(el) {
|
|
|
522
546
|
const combined = `${text} ${ariaLabel}`;
|
|
523
547
|
return DANGEROUS_KEYWORDS.some((kw) => combined.includes(kw));
|
|
524
548
|
}
|
|
525
|
-
function findEligibleEntryElements(containerSelector, excludeSelectors = []) {
|
|
526
|
-
const container = containerSelector ? document.querySelector(containerSelector) ?? document.body : document.body;
|
|
527
|
-
const candidates = container.querySelectorAll(
|
|
528
|
-
'a, button, [role="button"], nav a, nav button, .cta, [data-easter-entry]'
|
|
529
|
-
);
|
|
530
|
-
const excludeSet = /* @__PURE__ */ new Set();
|
|
531
|
-
for (const sel of excludeSelectors) {
|
|
532
|
-
document.querySelectorAll(sel).forEach((el) => excludeSet.add(el));
|
|
533
|
-
}
|
|
534
|
-
const eligible = [];
|
|
535
|
-
candidates.forEach((el) => {
|
|
536
|
-
if (excludeSet.has(el)) return;
|
|
537
|
-
if (!isElementVisible(el)) return;
|
|
538
|
-
if (isDangerousElement(el)) return;
|
|
539
|
-
const rect = el.getBoundingClientRect();
|
|
540
|
-
if (rect.width < 30 || rect.height < 20) return;
|
|
541
|
-
eligible.push(el);
|
|
542
|
-
});
|
|
543
|
-
return eligible;
|
|
544
|
-
}
|
|
545
549
|
function pickRandom(arr) {
|
|
546
550
|
if (arr.length === 0) return void 0;
|
|
547
551
|
return arr[Math.floor(Math.random() * arr.length)];
|
|
@@ -567,6 +571,15 @@ class HiddenEntry {
|
|
|
567
571
|
this._hintVisibleStart = 0;
|
|
568
572
|
this._hintCheckInterval = null;
|
|
569
573
|
this._hintVisibilityHandler = null;
|
|
574
|
+
this._domObserver = null;
|
|
575
|
+
this._intersectionObserver = null;
|
|
576
|
+
this._mutationDebounce = null;
|
|
577
|
+
this._navigationHandler = null;
|
|
578
|
+
this._bootstrapObserver = null;
|
|
579
|
+
this._retryTimer = null;
|
|
580
|
+
this._fallbackTimer = null;
|
|
581
|
+
this._retryCount = 0;
|
|
582
|
+
this._historyPatched = false;
|
|
570
583
|
this.config = config;
|
|
571
584
|
this.script = script;
|
|
572
585
|
this.onFound = onFound;
|
|
@@ -576,16 +589,10 @@ class HiddenEntry {
|
|
|
576
589
|
this._createFallbackEntry();
|
|
577
590
|
return;
|
|
578
591
|
}
|
|
579
|
-
const eligible = findEligibleEntryElements(
|
|
580
|
-
this.config.hiddenEntry.selector,
|
|
581
|
-
this.config.hiddenEntry.excludeSelectors
|
|
582
|
-
);
|
|
583
|
-
if (eligible.length === 0) {
|
|
584
|
-
this._createFallbackEntry();
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
this._injectIntoExisting(eligible);
|
|
588
592
|
this._startHintEscalation();
|
|
593
|
+
this._startBootstrapWatch();
|
|
594
|
+
this._scheduleFallback();
|
|
595
|
+
this._attemptInjection(0);
|
|
589
596
|
}
|
|
590
597
|
cleanup() {
|
|
591
598
|
var _a2, _b2, _c, _d;
|
|
@@ -600,6 +607,8 @@ class HiddenEntry {
|
|
|
600
607
|
this.targetElement.style.removeProperty("animation");
|
|
601
608
|
this.targetElement.classList.remove("eeq-entry-target");
|
|
602
609
|
}
|
|
610
|
+
this._stopWatchdog();
|
|
611
|
+
this._stopBootstrapWatch();
|
|
603
612
|
(_a2 = this._injectedElement) == null ? void 0 : _a2.remove();
|
|
604
613
|
this._injectedElement = null;
|
|
605
614
|
(_b2 = this.hintContainer) == null ? void 0 : _b2.remove();
|
|
@@ -612,17 +621,11 @@ class HiddenEntry {
|
|
|
612
621
|
this._attachToElement(this.targetElement);
|
|
613
622
|
}
|
|
614
623
|
// ─── Inject trigger text inside an existing element ─────────────────
|
|
615
|
-
_injectIntoExisting(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
).filter((el) => {
|
|
621
|
-
var _a2;
|
|
622
|
-
const text = ((_a2 = el.textContent) == null ? void 0 : _a2.trim()) ?? "";
|
|
623
|
-
const rect = el.getBoundingClientRect();
|
|
624
|
-
return text.length >= 2 && rect.width > 0 && rect.height > 0 && !el.closest("[data-eeq]");
|
|
625
|
-
});
|
|
624
|
+
_injectIntoExisting() {
|
|
625
|
+
var _a2;
|
|
626
|
+
const root = this._getSearchRoot();
|
|
627
|
+
const candidates = this._findTextCandidates(root);
|
|
628
|
+
if (candidates.length === 0) return false;
|
|
626
629
|
const sorted = candidates.sort((a, b) => {
|
|
627
630
|
const aNav = a.closest("nav") !== null || a.closest("header") !== null ? 1 : 0;
|
|
628
631
|
const bNav = b.closest("nav") !== null || b.closest("header") !== null ? 1 : 0;
|
|
@@ -633,8 +636,8 @@ class HiddenEntry {
|
|
|
633
636
|
});
|
|
634
637
|
const prints = sorted.map(
|
|
635
638
|
(el) => {
|
|
636
|
-
var
|
|
637
|
-
return `${el.tagName}:${(((
|
|
639
|
+
var _a3;
|
|
640
|
+
return `${el.tagName}:${(((_a3 = el.textContent) == null ? void 0 : _a3.trim()) ?? "").slice(0, 30)}`;
|
|
638
641
|
}
|
|
639
642
|
);
|
|
640
643
|
const HISTORY_KEY = "eeq_trigger_history";
|
|
@@ -666,8 +669,8 @@ class HiddenEntry {
|
|
|
666
669
|
];
|
|
667
670
|
const rotatedPrints = rotated.map(
|
|
668
671
|
(el) => {
|
|
669
|
-
var
|
|
670
|
-
return `${el.tagName}:${(((
|
|
672
|
+
var _a3;
|
|
673
|
+
return `${el.tagName}:${(((_a3 = el.textContent) == null ? void 0 : _a3.trim()) ?? "").slice(0, 30)}`;
|
|
671
674
|
}
|
|
672
675
|
);
|
|
673
676
|
const unused = rotated.filter((_, i) => !usedSet.has(rotatedPrints[i]));
|
|
@@ -681,6 +684,7 @@ class HiddenEntry {
|
|
|
681
684
|
const isInline = host.tagName === "A" || host.tagName === "BUTTON" || host.tagName === "LI" || host.closest("nav") !== null;
|
|
682
685
|
const span = document.createElement("span");
|
|
683
686
|
span.textContent = isInline ? " · start hunt" : " start hunt";
|
|
687
|
+
span.setAttribute("data-eeq", "trigger");
|
|
684
688
|
span.style.cssText = `
|
|
685
689
|
font: inherit;
|
|
686
690
|
color: inherit;
|
|
@@ -708,9 +712,264 @@ class HiddenEntry {
|
|
|
708
712
|
this._injectedElement = span;
|
|
709
713
|
this.targetElement = span;
|
|
710
714
|
this._attachToElement(span);
|
|
711
|
-
|
|
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();
|
|
721
|
+
this._startWatchdog();
|
|
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");
|
|
712
850
|
}
|
|
713
|
-
this.
|
|
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;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Watch for the injected trigger element being removed from the DOM
|
|
873
|
+
* (SPA re-render, route change, virtual scroll) or scrolled out of view.
|
|
874
|
+
*
|
|
875
|
+
* Uses IntersectionObserver (zero layout cost) for visibility,
|
|
876
|
+
* a MutationObserver on a stable ancestor (survives Angular/React re-renders),
|
|
877
|
+
* and navigation event listeners for SPA route changes.
|
|
878
|
+
*/
|
|
879
|
+
_startWatchdog() {
|
|
880
|
+
this._stopWatchdog();
|
|
881
|
+
if (!this._injectedElement) return;
|
|
882
|
+
this._intersectionObserver = new IntersectionObserver(
|
|
883
|
+
(entries) => {
|
|
884
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
885
|
+
const entry = entries[0];
|
|
886
|
+
if (entry && !entry.isIntersecting) {
|
|
887
|
+
this._reinject();
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
{ threshold: 0 }
|
|
891
|
+
);
|
|
892
|
+
this._intersectionObserver.observe(this._injectedElement);
|
|
893
|
+
const observeTarget = this._findStableAncestor(this._injectedElement);
|
|
894
|
+
this._domObserver = new MutationObserver(() => {
|
|
895
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
896
|
+
if (this._mutationDebounce) clearTimeout(this._mutationDebounce);
|
|
897
|
+
this._mutationDebounce = setTimeout(() => {
|
|
898
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
899
|
+
if (!document.body.contains(this._injectedElement)) {
|
|
900
|
+
this._reinject();
|
|
901
|
+
}
|
|
902
|
+
}, 300);
|
|
903
|
+
});
|
|
904
|
+
this._domObserver.observe(observeTarget, { childList: true, subtree: true });
|
|
905
|
+
this._navigationHandler = () => {
|
|
906
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
907
|
+
setTimeout(() => {
|
|
908
|
+
if (this._destroyed || !this._injectedElement) return;
|
|
909
|
+
if (!document.body.contains(this._injectedElement)) {
|
|
910
|
+
this._reinject();
|
|
911
|
+
}
|
|
912
|
+
}, 500);
|
|
913
|
+
};
|
|
914
|
+
window.addEventListener("popstate", this._navigationHandler);
|
|
915
|
+
window.addEventListener("hashchange", this._navigationHandler);
|
|
916
|
+
window.addEventListener("eeq:navigation", this._navigationHandler);
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Walk up the DOM to find a stable ancestor that won't be replaced
|
|
920
|
+
* by SPA framework routing (Angular <router-outlet>, React root, etc.).
|
|
921
|
+
*/
|
|
922
|
+
_findStableAncestor(el) {
|
|
923
|
+
let node = el.parentElement;
|
|
924
|
+
while (node && node !== document.body) {
|
|
925
|
+
const tag = node.tagName.toLowerCase();
|
|
926
|
+
if (tag === "main" || tag === "body" || tag.includes("app-") || // Angular app-root, app-component, etc.
|
|
927
|
+
tag.includes("-root") || // custom element roots
|
|
928
|
+
node.querySelector("router-outlet") !== null || node.id === "app" || node.id === "root" || node.id === "__next") {
|
|
929
|
+
return node;
|
|
930
|
+
}
|
|
931
|
+
node = node.parentElement;
|
|
932
|
+
}
|
|
933
|
+
return document.body;
|
|
934
|
+
}
|
|
935
|
+
_stopWatchdog() {
|
|
936
|
+
if (this._domObserver) {
|
|
937
|
+
this._domObserver.disconnect();
|
|
938
|
+
this._domObserver = null;
|
|
939
|
+
}
|
|
940
|
+
if (this._intersectionObserver) {
|
|
941
|
+
this._intersectionObserver.disconnect();
|
|
942
|
+
this._intersectionObserver = null;
|
|
943
|
+
}
|
|
944
|
+
if (this._mutationDebounce) {
|
|
945
|
+
clearTimeout(this._mutationDebounce);
|
|
946
|
+
this._mutationDebounce = null;
|
|
947
|
+
}
|
|
948
|
+
if (this._navigationHandler) {
|
|
949
|
+
window.removeEventListener("popstate", this._navigationHandler);
|
|
950
|
+
window.removeEventListener("hashchange", this._navigationHandler);
|
|
951
|
+
window.removeEventListener("eeq:navigation", this._navigationHandler);
|
|
952
|
+
this._navigationHandler = null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/** Remove old trigger (if still in DOM) and re-inject into a new element. */
|
|
956
|
+
_reinject() {
|
|
957
|
+
this._stopWatchdog();
|
|
958
|
+
if (this.clickHandler && this.targetElement) {
|
|
959
|
+
this.targetElement.removeEventListener("click", this.clickHandler);
|
|
960
|
+
}
|
|
961
|
+
if (this._injectedElement && document.body.contains(this._injectedElement)) {
|
|
962
|
+
this._injectedElement.remove();
|
|
963
|
+
}
|
|
964
|
+
this._injectedElement = null;
|
|
965
|
+
this.targetElement = null;
|
|
966
|
+
this._startBootstrapWatch();
|
|
967
|
+
this._scheduleFallback();
|
|
968
|
+
setTimeout(() => {
|
|
969
|
+
if (this._destroyed) return;
|
|
970
|
+
const ok = this._injectIntoExisting();
|
|
971
|
+
if (!ok) this._scheduleNextRetry();
|
|
972
|
+
}, 1200);
|
|
714
973
|
}
|
|
715
974
|
/**
|
|
716
975
|
* Try to insert the trigger span between words inside a text node.
|
|
@@ -718,12 +977,22 @@ class HiddenEntry {
|
|
|
718
977
|
* Returns true if inserted without layout shift.
|
|
719
978
|
*/
|
|
720
979
|
_tryInsertBetweenWords(host, span) {
|
|
721
|
-
var _a2;
|
|
722
980
|
const textNodes = [];
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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;
|
|
726
990
|
}
|
|
991
|
+
});
|
|
992
|
+
let current = walker.nextNode();
|
|
993
|
+
while (current) {
|
|
994
|
+
textNodes.push(current);
|
|
995
|
+
current = walker.nextNode();
|
|
727
996
|
}
|
|
728
997
|
if (!textNodes.length) return false;
|
|
729
998
|
const slotSeed = Math.floor(Date.now() / (1e3 * 60 * 2));
|
|
@@ -740,13 +1009,13 @@ class HiddenEntry {
|
|
|
740
1009
|
const splitAt = spacePositions[Math.min(idx, spacePositions.length - 1)];
|
|
741
1010
|
const before = text.slice(0, splitAt);
|
|
742
1011
|
const after = text.slice(splitAt);
|
|
743
|
-
const
|
|
1012
|
+
const rectBefore = host.getBoundingClientRect();
|
|
744
1013
|
const afterNode = document.createTextNode(after);
|
|
745
1014
|
textNode.textContent = before;
|
|
746
1015
|
host.insertBefore(afterNode, textNode.nextSibling);
|
|
747
1016
|
host.insertBefore(span, afterNode);
|
|
748
|
-
const
|
|
749
|
-
if (Math.abs(
|
|
1017
|
+
const rectAfter = host.getBoundingClientRect();
|
|
1018
|
+
if (Math.abs(rectAfter.height - rectBefore.height) > 8 || Math.abs(rectAfter.width - rectBefore.width) > 24) {
|
|
750
1019
|
host.removeChild(span);
|
|
751
1020
|
host.removeChild(afterNode);
|
|
752
1021
|
textNode.textContent = text;
|
|
@@ -991,6 +1260,7 @@ class HiddenEntry {
|
|
|
991
1260
|
document.head.appendChild(this.shimmerStyle);
|
|
992
1261
|
}
|
|
993
1262
|
_createFallbackEntry() {
|
|
1263
|
+
if (this.fallbackButton) return;
|
|
994
1264
|
this.fallbackButton = document.createElement("button");
|
|
995
1265
|
this.fallbackButton.textContent = "·";
|
|
996
1266
|
this.fallbackButton.setAttribute("aria-label", "Hidden game entry");
|
|
@@ -1059,7 +1329,7 @@ class NarrativeRenderer {
|
|
|
1059
1329
|
width: "100%",
|
|
1060
1330
|
height: "100%",
|
|
1061
1331
|
pointerEvents: "none",
|
|
1062
|
-
zIndex: "
|
|
1332
|
+
zIndex: "999993"
|
|
1063
1333
|
});
|
|
1064
1334
|
document.body.appendChild(this.host);
|
|
1065
1335
|
this.shadow = this.host.attachShadow({ mode: "closed" });
|
|
@@ -1889,6 +2159,13 @@ class ThreeRenderer {
|
|
|
1889
2159
|
}
|
|
1890
2160
|
}
|
|
1891
2161
|
}
|
|
2162
|
+
/** Mark all ambient particles for rapid fade-out (rhythm lost). */
|
|
2163
|
+
fadeOutAmbientParticles() {
|
|
2164
|
+
for (const p of this._ambientParticles) {
|
|
2165
|
+
p.fadeIn = false;
|
|
2166
|
+
if (p.life > 0.3) p.life = 0.3;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
1892
2169
|
/** Start the finale composition. */
|
|
1893
2170
|
startFinale() {
|
|
1894
2171
|
this._finaleActive = true;
|
|
@@ -2209,17 +2486,17 @@ class ThreeRenderer {
|
|
|
2209
2486
|
this.eggBodies.push(body3);
|
|
2210
2487
|
}
|
|
2211
2488
|
// ── Mini egg helpers for particles ─────────────────────────────────────
|
|
2212
|
-
/** Create a tiny egg-shaped geometry
|
|
2489
|
+
/** Create a tiny egg-shaped geometry — rounder, less elongated. */
|
|
2213
2490
|
_createMiniEggGeo(T, radius) {
|
|
2214
|
-
const geo = new T.SphereGeometry(radius,
|
|
2491
|
+
const geo = new T.SphereGeometry(radius, 12, 12);
|
|
2215
2492
|
const pos = geo.attributes.position;
|
|
2216
2493
|
for (let i = 0; i < pos.count; i++) {
|
|
2217
2494
|
let y = pos.getY(i);
|
|
2218
2495
|
const x = pos.getX(i);
|
|
2219
2496
|
const z = pos.getZ(i);
|
|
2220
|
-
const topSquash = y > 0 ? 0.
|
|
2221
|
-
y = y * 1.
|
|
2222
|
-
const narrowing = 1 - Math.abs(y) * 0.
|
|
2497
|
+
const topSquash = y > 0 ? 0.95 : 1;
|
|
2498
|
+
y = y * 1.08 * topSquash;
|
|
2499
|
+
const narrowing = 1 - Math.abs(y) * 0.03;
|
|
2223
2500
|
pos.setX(i, x * narrowing);
|
|
2224
2501
|
pos.setY(i, y);
|
|
2225
2502
|
pos.setZ(i, z * narrowing);
|
|
@@ -2235,8 +2512,8 @@ class ThreeRenderer {
|
|
|
2235
2512
|
c.height = 64;
|
|
2236
2513
|
const ctx = c.getContext("2d");
|
|
2237
2514
|
const hue = Math.random() * 360;
|
|
2238
|
-
const sat =
|
|
2239
|
-
const light =
|
|
2515
|
+
const sat = 60 + Math.random() * 25;
|
|
2516
|
+
const light = 68 + Math.random() * 14;
|
|
2240
2517
|
ctx.fillStyle = `hsl(${hue}, ${sat}%, ${light}%)`;
|
|
2241
2518
|
ctx.fillRect(0, 0, 64, 64);
|
|
2242
2519
|
const accentHue = (hue + 80 + Math.random() * 100) % 360;
|
|
@@ -2307,7 +2584,7 @@ class ThreeRenderer {
|
|
|
2307
2584
|
return new T.MeshBasicMaterial({
|
|
2308
2585
|
map: tex,
|
|
2309
2586
|
transparent: true,
|
|
2310
|
-
opacity
|
|
2587
|
+
opacity: opacity || 0.85
|
|
2311
2588
|
});
|
|
2312
2589
|
}
|
|
2313
2590
|
_createEggGeometry(T) {
|
|
@@ -3037,12 +3314,12 @@ class ThreeRenderer {
|
|
|
3037
3314
|
p.mesh.rotation.z += dt * 0.5;
|
|
3038
3315
|
if (p.fadeIn && p.life > 0.7) {
|
|
3039
3316
|
p.mesh.material.opacity = Math.min(
|
|
3040
|
-
0.
|
|
3041
|
-
p.mesh.material.opacity + dt *
|
|
3317
|
+
0.9,
|
|
3318
|
+
p.mesh.material.opacity + dt * 1
|
|
3042
3319
|
);
|
|
3043
|
-
if (p.mesh.material.opacity >= 0.
|
|
3320
|
+
if (p.mesh.material.opacity >= 0.85) p.fadeIn = false;
|
|
3044
3321
|
} else {
|
|
3045
|
-
p.mesh.material.opacity = Math.max(0, p.life * 0.
|
|
3322
|
+
p.mesh.material.opacity = Math.max(0, p.life * 0.85);
|
|
3046
3323
|
}
|
|
3047
3324
|
if (p.life <= 0) {
|
|
3048
3325
|
this.scene.remove(p.mesh);
|
|
@@ -4511,7 +4788,7 @@ class RhythmStage {
|
|
|
4511
4788
|
if (isGood) {
|
|
4512
4789
|
this._goodCycles++;
|
|
4513
4790
|
} else {
|
|
4514
|
-
this._goodCycles = 0;
|
|
4791
|
+
this._goodCycles = Math.max(0, this._goodCycles - 1);
|
|
4515
4792
|
this._showReaction();
|
|
4516
4793
|
}
|
|
4517
4794
|
this._lastPhaseCount += 2;
|
|
@@ -4829,11 +5106,13 @@ class GameController {
|
|
|
4829
5106
|
(_a3 = this.eggRenderer) == null ? void 0 : _a3.addTrail(data.x, data.y, data.velocity);
|
|
4830
5107
|
});
|
|
4831
5108
|
this.bus.on("rhythm:breathe", (data) => {
|
|
4832
|
-
var _a3, _b3
|
|
5109
|
+
var _a3, _b3;
|
|
4833
5110
|
(_a3 = this.eggRenderer) == null ? void 0 : _a3.setBreathIntensity(data.accuracy);
|
|
4834
5111
|
(_b3 = this.pageBreather) == null ? void 0 : _b3.update(data.accuracy, data.isMoving);
|
|
4835
|
-
if (
|
|
5112
|
+
if (data.accuracy > 0 && this.threeRenderer) {
|
|
4836
5113
|
this.threeRenderer.showProgressParticles(0.6 + data.accuracy * 0.4);
|
|
5114
|
+
} else if (this.threeRenderer) {
|
|
5115
|
+
this.threeRenderer.fadeOutAmbientParticles();
|
|
4837
5116
|
}
|
|
4838
5117
|
});
|
|
4839
5118
|
this.overlay = new OverlayManager(this.config);
|
|
@@ -5151,6 +5430,7 @@ class GameController {
|
|
|
5151
5430
|
}
|
|
5152
5431
|
const successLines = eggIndex === 0 ? this.script.stage1Success : eggIndex === 1 ? this.script.stage2Success : this.script.stage3Success;
|
|
5153
5432
|
await ((_c = this.narrative) == null ? void 0 : _c.showSequence(successLines, 4500));
|
|
5433
|
+
await this._wait(3e3);
|
|
5154
5434
|
(_d = this.narrative) == null ? void 0 : _d.clear();
|
|
5155
5435
|
await this._wait(800);
|
|
5156
5436
|
if (this.threeRenderer) {
|