easter-egg-quest 1.0.24 → 1.0.26

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.
@@ -567,6 +567,8 @@ class HiddenEntry {
567
567
  this.clickHandler = null;
568
568
  this.fallbackButton = null;
569
569
  this._injectedElement = null;
570
+ this._replacedTextNode = null;
571
+ this._replacedTextContent = "";
570
572
  this._destroyed = false;
571
573
  this._hintVisibleElapsed = 0;
572
574
  this._hintVisibleStart = 0;
@@ -610,6 +612,7 @@ class HiddenEntry {
610
612
  }
611
613
  this._stopWatchdog();
612
614
  this._stopBootstrapWatch();
615
+ this._restoreReplacedText();
613
616
  (_a2 = this._injectedElement) == null ? void 0 : _a2.remove();
614
617
  this._injectedElement = null;
615
618
  (_b2 = this.hintContainer) == null ? void 0 : _b2.remove();
@@ -662,12 +665,9 @@ class HiddenEntry {
662
665
  const navEls = sorted.filter(
663
666
  (el) => el.closest("nav") !== null || el.closest("header") !== null
664
667
  );
665
- const offset = bodyEls.length > 1 ? visitCount % bodyEls.length : 0;
666
- const rotated = [
667
- ...bodyEls.slice(offset),
668
- ...bodyEls.slice(0, offset),
669
- ...navEls
670
- ];
668
+ const rotated = this._shuffleCandidates(bodyEls, visitCount).concat(
669
+ this._shuffleCandidates(navEls, visitCount + 17)
670
+ );
671
671
  const rotatedPrints = rotated.map(
672
672
  (el) => {
673
673
  var _a3;
@@ -682,26 +682,9 @@ class HiddenEntry {
682
682
  const ordered = unused.length > 0 ? [...unused, ...rotated.filter((_, i) => usedSet.has(rotatedPrints[i]))] : rotated;
683
683
  for (const candidate of ordered) {
684
684
  const host = candidate;
685
- const isInline = host.tagName === "A" || host.tagName === "BUTTON" || host.tagName === "LI" || host.closest("nav") !== null;
686
- const span = document.createElement("span");
687
- span.textContent = isInline ? " · start hunt" : " start hunt";
688
- span.setAttribute("data-eeq", "trigger");
689
- span.style.cssText = `
690
- font: inherit;
691
- color: inherit;
692
- letter-spacing: inherit;
693
- cursor: pointer;
694
- `;
695
- const inserted = !isInline && this._tryInsertBetweenWords(host, span);
696
- if (!inserted) {
697
- const heightBefore = host.getBoundingClientRect().height;
698
- host.appendChild(span);
699
- const heightAfter = host.getBoundingClientRect().height;
700
- if (Math.abs(heightAfter - heightBefore) > 2) {
701
- host.removeChild(span);
702
- continue;
703
- }
704
- }
685
+ if (!this._canHostInjectedTrigger(host)) continue;
686
+ const span = this._replaceTextCandidate(host);
687
+ if (!span) continue;
705
688
  const idx = sorted.indexOf(candidate);
706
689
  if (idx >= 0) {
707
690
  usedSet.add(prints[idx]);
@@ -814,9 +797,53 @@ class HiddenEntry {
814
797
  if (childElementCount > 12) return false;
815
798
  const rect = el.getBoundingClientRect();
816
799
  if (rect.width < 18 || rect.height < 12) return false;
800
+ if (!this._canHostInjectedTrigger(el)) return false;
817
801
  return true;
818
802
  });
819
803
  }
804
+ _shuffleCandidates(candidates, seed) {
805
+ if (candidates.length < 2) return [...candidates];
806
+ const arr = [...candidates];
807
+ let state = (seed || 1) ^ Date.now() & 65535;
808
+ const rand = () => {
809
+ state ^= state << 13;
810
+ state ^= state >>> 17;
811
+ state ^= state << 5;
812
+ return (state >>> 0) % 1e4 / 1e4;
813
+ };
814
+ for (let i = arr.length - 1; i > 0; i--) {
815
+ const j = Math.floor(rand() * (i + 1));
816
+ [arr[i], arr[j]] = [arr[j], arr[i]];
817
+ }
818
+ return arr;
819
+ }
820
+ _canHostInjectedTrigger(host) {
821
+ const style = getComputedStyle(host);
822
+ const textOverflow = style.textOverflow;
823
+ const whiteSpace = style.whiteSpace;
824
+ const overflowX = style.overflowX;
825
+ const overflow = style.overflow;
826
+ if (textOverflow === "ellipsis") return false;
827
+ if (whiteSpace === "nowrap" && ["hidden", "clip"].includes(overflowX || overflow)) return false;
828
+ if (host.scrollWidth > host.clientWidth + 2 && host.clientWidth > 0) return false;
829
+ if (host.scrollHeight > host.clientHeight + 2 && host.clientHeight > 0 && whiteSpace === "nowrap") return false;
830
+ return true;
831
+ }
832
+ _isTriggerClipped(host, span) {
833
+ if (!document.body.contains(span)) return true;
834
+ const hostRect = host.getBoundingClientRect();
835
+ const spanRect = span.getBoundingClientRect();
836
+ const style = getComputedStyle(host);
837
+ const clipsHorizontally = ["hidden", "clip"].includes(style.overflowX || style.overflow);
838
+ const clipsVertically = ["hidden", "clip"].includes(style.overflowY || style.overflow);
839
+ if (host.scrollWidth > host.clientWidth + 2 && host.clientWidth > 0) return true;
840
+ if (style.textOverflow === "ellipsis") return true;
841
+ if (clipsHorizontally && spanRect.right > hostRect.right - 1) return true;
842
+ if (clipsHorizontally && spanRect.left < hostRect.left + 1) return true;
843
+ if (clipsVertically && spanRect.bottom > hostRect.bottom - 1) return true;
844
+ if (clipsVertically && spanRect.top < hostRect.top + 1) return true;
845
+ return false;
846
+ }
820
847
  _startBootstrapWatch() {
821
848
  this._stopBootstrapWatch();
822
849
  const root = this._getSearchRoot();
@@ -962,6 +989,7 @@ class HiddenEntry {
962
989
  if (this._injectedElement && document.body.contains(this._injectedElement)) {
963
990
  this._injectedElement.remove();
964
991
  }
992
+ this._restoreReplacedText();
965
993
  this._injectedElement = null;
966
994
  this.targetElement = null;
967
995
  this._startBootstrapWatch();
@@ -982,8 +1010,8 @@ class HiddenEntry {
982
1010
  const walker = document.createTreeWalker(host, NodeFilter.SHOW_TEXT, {
983
1011
  acceptNode: (node) => {
984
1012
  var _a2;
985
- const text2 = ((_a2 = node.textContent) == null ? void 0 : _a2.trim()) ?? "";
986
- if (text2.length <= 5) return NodeFilter.FILTER_REJECT;
1013
+ const text = ((_a2 = node.textContent) == null ? void 0 : _a2.trim()) ?? "";
1014
+ if (text.length <= 5) return NodeFilter.FILTER_REJECT;
987
1015
  const parent = node.parentElement;
988
1016
  if (!parent) return NodeFilter.FILTER_REJECT;
989
1017
  if (parent.closest("[data-eeq], script, style, noscript")) return NodeFilter.FILTER_REJECT;
@@ -996,33 +1024,135 @@ class HiddenEntry {
996
1024
  current = walker.nextNode();
997
1025
  }
998
1026
  if (!textNodes.length) return false;
999
- const slotSeed = Math.floor(Date.now() / (1e3 * 60 * 2));
1000
- const textNode = textNodes[slotSeed % textNodes.length];
1001
- const text = textNode.textContent ?? "";
1002
- const spacePositions = [];
1003
- for (let i = 1; i < text.length - 1; i++) {
1004
- if (text[i] === " ") spacePositions.push(i);
1005
- }
1006
- if (spacePositions.length < 2) return false;
1007
- const start = Math.floor(spacePositions.length * 0.25);
1008
- const end = Math.floor(spacePositions.length * 0.75);
1009
- const idx = start + slotSeed % Math.max(1, end - start);
1010
- const splitAt = spacePositions[Math.min(idx, spacePositions.length - 1)];
1011
- const before = text.slice(0, splitAt);
1012
- const after = text.slice(splitAt);
1027
+ const textNodesOrdered = this._shuffleCandidates(
1028
+ textNodes.map((node) => node.parentElement).filter((el) => !!el),
1029
+ Date.now()
1030
+ );
1031
+ const uniqueTextNodes = textNodesOrdered.map((parent) => textNodes.find((node) => node.parentElement === parent)).filter((node) => !!node);
1032
+ for (const textNode of uniqueTextNodes) {
1033
+ const text = textNode.textContent ?? "";
1034
+ const parent = textNode.parentElement;
1035
+ if (!parent) continue;
1036
+ const spacePositions = [];
1037
+ for (let i = 1; i < text.length - 1; i++) {
1038
+ if (text[i] === " ") spacePositions.push(i);
1039
+ }
1040
+ if (spacePositions.length < 2) continue;
1041
+ const start = Math.floor(spacePositions.length * 0.2);
1042
+ const end = Math.ceil(spacePositions.length * 0.8);
1043
+ const candidateBreaks = this._shuffleNumbers(
1044
+ spacePositions.slice(start, Math.max(start + 1, end))
1045
+ );
1046
+ for (const splitAt of candidateBreaks) {
1047
+ const before = text.slice(0, splitAt);
1048
+ const after = text.slice(splitAt);
1049
+ const rectBefore = host.getBoundingClientRect();
1050
+ const afterNode = document.createTextNode(after);
1051
+ this._applyTriggerTypography(span, parent);
1052
+ textNode.textContent = before;
1053
+ parent.insertBefore(span, textNode.nextSibling);
1054
+ parent.insertBefore(afterNode, span.nextSibling);
1055
+ const rectAfter = host.getBoundingClientRect();
1056
+ const invalid = Math.abs(rectAfter.height - rectBefore.height) > 8 || Math.abs(rectAfter.width - rectBefore.width) > 24 || this._isTriggerClipped(host, span);
1057
+ if (!invalid) return true;
1058
+ span.remove();
1059
+ afterNode.remove();
1060
+ textNode.textContent = text;
1061
+ }
1062
+ }
1063
+ return false;
1064
+ }
1065
+ _shuffleNumbers(values) {
1066
+ const arr = [...values];
1067
+ for (let i = arr.length - 1; i > 0; i--) {
1068
+ const j = Math.floor(Math.random() * (i + 1));
1069
+ [arr[i], arr[j]] = [arr[j], arr[i]];
1070
+ }
1071
+ return arr;
1072
+ }
1073
+ _replaceTextCandidate(host) {
1074
+ const target = this._findReplacementTextTarget(host);
1075
+ if (!target) return null;
1076
+ const span = document.createElement("span");
1077
+ span.textContent = "start hunt";
1078
+ span.setAttribute("data-eeq", "trigger");
1079
+ span.classList.add("eeq-inline-trigger");
1080
+ this._applyTriggerTypography(span, target.parent);
1013
1081
  const rectBefore = host.getBoundingClientRect();
1014
- const afterNode = document.createTextNode(after);
1015
- textNode.textContent = before;
1016
- host.insertBefore(afterNode, textNode.nextSibling);
1017
- host.insertBefore(span, afterNode);
1082
+ target.textNode.textContent = "";
1083
+ target.parent.insertBefore(span, target.textNode.nextSibling);
1018
1084
  const rectAfter = host.getBoundingClientRect();
1019
- if (Math.abs(rectAfter.height - rectBefore.height) > 8 || Math.abs(rectAfter.width - rectBefore.width) > 24) {
1020
- host.removeChild(span);
1021
- host.removeChild(afterNode);
1022
- textNode.textContent = text;
1023
- return false;
1085
+ const invalid = Math.abs(rectAfter.height - rectBefore.height) > 6 || Math.abs(rectAfter.width - rectBefore.width) > 20 || this._isTriggerClipped(host, span);
1086
+ if (invalid) {
1087
+ span.remove();
1088
+ target.textNode.textContent = target.text;
1089
+ return null;
1024
1090
  }
1025
- return true;
1091
+ this._replacedTextNode = target.textNode;
1092
+ this._replacedTextContent = target.text;
1093
+ return span;
1094
+ }
1095
+ _findReplacementTextTarget(host) {
1096
+ const triggerLength = "start hunt".length;
1097
+ const textTargets = [];
1098
+ const walker = document.createTreeWalker(host, NodeFilter.SHOW_TEXT, {
1099
+ acceptNode: (node) => {
1100
+ const parent = node.parentElement;
1101
+ const text = this._normalizeComparableText(node.textContent ?? "");
1102
+ if (!parent || text.length === 0) return NodeFilter.FILTER_REJECT;
1103
+ if (parent.closest("[data-eeq], script, style, noscript")) return NodeFilter.FILTER_REJECT;
1104
+ if (!isElementVisible(parent)) return NodeFilter.FILTER_REJECT;
1105
+ if (Math.abs(text.length - triggerLength) > 10) return NodeFilter.FILTER_REJECT;
1106
+ return NodeFilter.FILTER_ACCEPT;
1107
+ }
1108
+ });
1109
+ let current = walker.nextNode();
1110
+ while (current) {
1111
+ const textNode = current;
1112
+ const parent = textNode.parentElement;
1113
+ const text = textNode.textContent ?? "";
1114
+ if (parent) {
1115
+ textTargets.push({ textNode, parent, text });
1116
+ }
1117
+ current = walker.nextNode();
1118
+ }
1119
+ if (!textTargets.length) return null;
1120
+ const shuffled = this._shuffleTextTargets(textTargets);
1121
+ return shuffled[0] ?? null;
1122
+ }
1123
+ _shuffleTextTargets(values) {
1124
+ const arr = [...values];
1125
+ for (let i = arr.length - 1; i > 0; i--) {
1126
+ const j = Math.floor(Math.random() * (i + 1));
1127
+ [arr[i], arr[j]] = [arr[j], arr[i]];
1128
+ }
1129
+ return arr;
1130
+ }
1131
+ _normalizeComparableText(text) {
1132
+ return text.replace(/\s+/g, " ").trim();
1133
+ }
1134
+ _restoreReplacedText() {
1135
+ if (this._replacedTextNode) {
1136
+ this._replacedTextNode.textContent = this._replacedTextContent;
1137
+ }
1138
+ this._replacedTextNode = null;
1139
+ this._replacedTextContent = "";
1140
+ }
1141
+ _applyTriggerTypography(span, source) {
1142
+ const style = getComputedStyle(source);
1143
+ span.style.cssText = `
1144
+ font-family: ${style.fontFamily};
1145
+ font-size: ${style.fontSize};
1146
+ font-weight: ${style.fontWeight};
1147
+ font-style: ${style.fontStyle};
1148
+ line-height: ${style.lineHeight};
1149
+ letter-spacing: ${style.letterSpacing};
1150
+ text-transform: ${style.textTransform};
1151
+ color: ${style.color};
1152
+ cursor: pointer;
1153
+ white-space: nowrap;
1154
+ transition: color 0.18s ease;
1155
+ `;
1026
1156
  }
1027
1157
  // ─── Shared ───────────────────────────────────────────────────────────
1028
1158
  _attachToElement(el) {
@@ -1214,6 +1344,9 @@ class HiddenEntry {
1214
1344
  .eeq-entry-target {
1215
1345
  animation: eeq-shimmer 3s ease-in-out infinite !important;
1216
1346
  }
1347
+ .eeq-inline-trigger:hover {
1348
+ color: ${this.config.theme.accent};
1349
+ }
1217
1350
  @keyframes eeq-shimmer {
1218
1351
  0%, 100% { opacity: 1; }
1219
1352
  50% { opacity: 0.55; }