@genex-ai/embed-sdk 0.8.0 → 0.10.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.
@@ -621,19 +621,91 @@ function enterBlocked() {
621
621
  }
622
622
  var OVERLAY_TEXT = {
623
623
  // Delayed (text grace period): most connects finish before the text ever
624
- // shows — a sub-second text flash reads as a glitch.
625
- connecting: { title: "Connecting\u2026" },
626
- // Same copy as `connecting` so the whole standalone bounce (gate ->
624
+ // shows — a sub-second text flash reads as a glitch. `wordmark` states show
625
+ // GENEX in the breathing decrypt loop instead of an instruction.
626
+ connecting: { title: "GENEX", wordmark: true },
627
+ // Same treatment as `connecting` so the whole standalone bounce (gate ->
627
628
  // /play/authorize -> gate) reads as ONE continuous screen. Still neutral:
628
629
  // the automatic bounce precedes GUEST play as often as sign-in.
629
- redirecting: { title: "Connecting\u2026" },
630
+ redirecting: { title: "GENEX", wordmark: true },
630
631
  // Terminal states need the visitor to act — text shows immediately.
631
632
  "blocked-standalone": { title: "Sign in to play", button: "Sign in", instant: true },
632
633
  "blocked-embedded": { title: "Sign in on the Genex dashboard to continue", instant: true }
633
634
  };
634
- var OVERLAY_FONT_URL = "https://cdn.genex.technology/fonts/Geist-variable.woff2";
635
- var OVERLAY_FONT_STACK = "'Geist',system-ui,-apple-system,sans-serif";
635
+ var OVERLAY_FONT_URL = "https://cdn.genex.technology/fonts/GeistMono-variable.woff2";
636
+ var OVERLAY_FONT_STACK = "'Geist Mono',ui-monospace,SFMono-Regular,Menlo,monospace";
636
637
  var overlayFontRequested = false;
638
+ var SCRAMBLE_NOISE = "!<>-_/[]{}=+*^?#$%&@";
639
+ var SCRAMBLE_TICK_MS = 50;
640
+ var SCRAMBLE_SETTLE_TICKS = 10;
641
+ var WORDMARK_LOOP_PHASES = [
642
+ { name: "churn", ticks: 5 },
643
+ { name: "settle", ticks: 10 },
644
+ { name: "hold", ticks: 12 },
645
+ { name: "dissolve", ticks: 8 }
646
+ ];
647
+ function scrambleNoise(length) {
648
+ let out = "";
649
+ for (let i = 0; i < length; i++) {
650
+ out += SCRAMBLE_NOISE[Math.floor(Math.random() * SCRAMBLE_NOISE.length)];
651
+ }
652
+ return out;
653
+ }
654
+ function scrambleStatic() {
655
+ try {
656
+ const w = win();
657
+ if (!w || typeof w.matchMedia !== "function") return true;
658
+ return w.matchMedia("(prefers-reduced-motion: reduce)").matches;
659
+ } catch {
660
+ return true;
661
+ }
662
+ }
663
+ function scrambleIn(el, text) {
664
+ if (scrambleStatic()) {
665
+ el.textContent = text;
666
+ return () => {
667
+ };
668
+ }
669
+ let i = 0;
670
+ el.textContent = scrambleNoise(text.length);
671
+ const id = setInterval(() => {
672
+ i++;
673
+ if (i >= SCRAMBLE_SETTLE_TICKS) {
674
+ el.textContent = text;
675
+ clearInterval(id);
676
+ return;
677
+ }
678
+ const lock = Math.floor(i / SCRAMBLE_SETTLE_TICKS * text.length);
679
+ el.textContent = text.slice(0, lock) + scrambleNoise(text.length - lock);
680
+ }, SCRAMBLE_TICK_MS);
681
+ return () => clearInterval(id);
682
+ }
683
+ function wordmarkLoop(el, text) {
684
+ if (scrambleStatic()) {
685
+ el.textContent = text;
686
+ return () => {
687
+ };
688
+ }
689
+ let phase = 0;
690
+ let i = 0;
691
+ const frame = () => {
692
+ const p = WORDMARK_LOOP_PHASES[phase];
693
+ let kept;
694
+ if (p.name === "churn") kept = 0;
695
+ else if (p.name === "settle") kept = Math.floor(i / p.ticks * text.length);
696
+ else if (p.name === "hold") kept = text.length;
697
+ else kept = text.length - Math.floor(i / p.ticks * text.length);
698
+ el.textContent = text.slice(0, kept) + scrambleNoise(text.length - kept);
699
+ i++;
700
+ if (i >= p.ticks) {
701
+ i = 0;
702
+ phase = (phase + 1) % WORDMARK_LOOP_PHASES.length;
703
+ }
704
+ };
705
+ frame();
706
+ const id = setInterval(frame, SCRAMBLE_TICK_MS);
707
+ return () => clearInterval(id);
708
+ }
637
709
  function ensureOverlayFont(d) {
638
710
  if (overlayFontRequested) return;
639
711
  overlayFontRequested = true;
@@ -641,7 +713,7 @@ function ensureOverlayFont(d) {
641
713
  const FF = win()?.FontFace;
642
714
  const fonts = d.fonts;
643
715
  if (!FF || !fonts?.add) return;
644
- const face = new FF("Geist", `url(${OVERLAY_FONT_URL}) format('woff2')`, {
716
+ const face = new FF("Geist Mono", `url(${OVERLAY_FONT_URL}) format('woff2')`, {
645
717
  weight: "100 900",
646
718
  display: "swap"
647
719
  });
@@ -664,7 +736,7 @@ function removeOverlay(instant = false) {
664
736
  const el = overlayEl;
665
737
  overlayEl = null;
666
738
  if (!el) return;
667
- el.clearTextTimer();
739
+ el.stopText();
668
740
  const root = el.root;
669
741
  if (instant || !root.style) {
670
742
  root.remove?.();
@@ -686,12 +758,12 @@ function buildOverlay(d) {
686
758
  ensureOverlayFont(d);
687
759
  const root = d.createElement("div");
688
760
  root.setAttribute("data-genex-embed-overlay", "");
689
- root.style.cssText = `position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:16px;background:#000;color:#fff;font-family:${OVERLAY_FONT_STACK};text-align:center;pointer-events:auto;user-select:none`;
761
+ root.style.cssText = `position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:14px;background:#000;color:#fff;font-family:${OVERLAY_FONT_STACK};text-align:center;pointer-events:auto;user-select:none`;
690
762
  const title = d.createElement("div");
691
- title.style.cssText = "font-size:18px;font-weight:600;padding:0 24px;opacity:0";
763
+ title.style.cssText = "font-size:13px;font-weight:400;line-height:1.3;padding:0 24px;opacity:0;min-height:1.3em;font-variant-ligatures:none";
692
764
  root.appendChild(title);
693
765
  const button = d.createElement("button");
694
- button.style.cssText = "font-size:15px;font-weight:600;padding:10px 28px;border-radius:9px;border:none;cursor:pointer;background:#4b60bf;color:#fff;display:none";
766
+ button.style.cssText = "font-family:inherit;font-size:13px;font-weight:500;line-height:1.3;padding:8px 22px;border-radius:9px;border:none;cursor:pointer;background:#4b60bf;color:#fff;display:none";
695
767
  button.addEventListener("mouseenter", () => {
696
768
  button.style.background = "#3f51a8";
697
769
  });
@@ -712,29 +784,37 @@ function buildOverlay(d) {
712
784
  if (d.body) mount();
713
785
  else d.addEventListener?.("DOMContentLoaded", mount);
714
786
  let textTimer;
715
- const clearTextTimer = () => {
787
+ let cancelScramble;
788
+ const stopText = () => {
716
789
  if (textTimer !== void 0) {
717
790
  clearTimeout(textTimer);
718
791
  textTimer = void 0;
719
792
  }
793
+ if (cancelScramble) {
794
+ cancelScramble();
795
+ cancelScramble = void 0;
796
+ }
720
797
  };
721
798
  return {
722
799
  root,
723
- clearTextTimer,
800
+ stopText,
724
801
  setContent(kind) {
725
802
  const text = OVERLAY_TEXT[kind];
726
- clearTextTimer();
803
+ stopText();
727
804
  title.textContent = text.title;
728
- if (text.instant || overlayTextDelayMs <= 0) {
729
- title.style.transition = "";
805
+ title.style.letterSpacing = text.wordmark ? "0.22em" : "";
806
+ title.style.marginRight = text.wordmark ? "-0.22em" : "";
807
+ const reveal = () => {
730
808
  title.style.opacity = "1";
809
+ cancelScramble = text.wordmark ? wordmarkLoop(title, text.title) : scrambleIn(title, text.title);
810
+ };
811
+ if (text.instant || overlayTextDelayMs <= 0) {
812
+ reveal();
731
813
  } else {
732
- title.style.transition = "";
733
814
  title.style.opacity = "0";
734
815
  textTimer = setTimeout(() => {
735
816
  textTimer = void 0;
736
- title.style.transition = "opacity 200ms ease";
737
- title.style.opacity = "1";
817
+ reveal();
738
818
  }, overlayTextDelayMs);
739
819
  }
740
820
  button.style.display = text.button ? "inline-block" : "none";
@@ -769,7 +849,7 @@ function buildGuestPopover(d) {
769
849
  root.appendChild(text);
770
850
  const signIn = d.createElement("button");
771
851
  signIn.textContent = "Sign in";
772
- signIn.style.cssText = "font-size:13px;font-weight:600;padding:6px 16px;border-radius:9px;border:none;cursor:pointer;background:#4b60bf;color:#fff";
852
+ signIn.style.cssText = "font-family:inherit;font-size:13px;font-weight:500;padding:6px 16px;border-radius:9px;border:none;cursor:pointer;background:#4b60bf;color:#fff";
773
853
  signIn.addEventListener("mouseenter", () => {
774
854
  signIn.style.background = "#3f51a8";
775
855
  });
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  submitScore,
18
18
  waitForAuth,
19
19
  waitForPlayer
20
- } from "./chunk-BWJ2FVCH.js";
20
+ } from "./chunk-Z7VGKJYX.js";
21
21
  export {
22
22
  __resetForTests,
23
23
  _stashTicketFromUrl,
package/dist/sentry.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  _stashTicketFromUrl,
3
3
  getUser,
4
4
  on
5
- } from "./chunk-BWJ2FVCH.js";
5
+ } from "./chunk-Z7VGKJYX.js";
6
6
 
7
7
  // src/sentry.ts
8
8
  import * as Sentry from "@sentry/browser";
@@ -16,18 +16,39 @@ function scrubTokens(s) {
16
16
  // src/sentry.ts
17
17
  var DEFAULT_GAMES_DSN = "https://d1414cfcdc09bf1c1d124e86f8087251@o4511115493900288.ingest.us.sentry.io/4511662569160704";
18
18
  var initialized = false;
19
+ function isTouchDevice() {
20
+ try {
21
+ const coarse = matchMedia("(pointer: coarse)").matches;
22
+ const touch = navigator.maxTouchPoints > 1;
23
+ const nav = navigator;
24
+ const mobileUA = nav.userAgentData ? !!nav.userAgentData.mobile : /Mobi|Android/i.test(navigator.userAgent);
25
+ return touch && coarse || mobileUA;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ var touchSession = false;
19
31
  function initGameSentry(opts) {
20
32
  if (typeof window === "undefined") return;
21
33
  if (initialized) return;
22
34
  initialized = true;
35
+ touchSession = isTouchDevice();
36
+ try {
37
+ window.__GENEX_SDK_SENTRY__ = "touch-aware";
38
+ } catch {
39
+ }
23
40
  _stashTicketFromUrl();
24
41
  Sentry.init({
25
42
  dsn: opts.dsn ?? DEFAULT_GAMES_DSN,
26
43
  environment: opts.environment ?? "production",
27
- tracesSampleRate: 1,
28
- // low-traffic demo; first knobs to lower: replays, then this
29
- replaysSessionSampleRate: 1,
30
- // record EVERY session (demo decision 2026-07-02)
44
+ // Mobile-readiness program (2026-07-21): touch devices get throttled
45
+ // telemetry — this REVERSES the 2026-07-02 record-every-session decision
46
+ // for the touch device class only, because 100% replay + canvas readback
47
+ // burns memory/GPU on the phones the crash program is trying to save.
48
+ // Desktop keeps full rates; on-error replays stay 100% everywhere, so the
49
+ // crash-debugging case keeps its data.
50
+ tracesSampleRate: touchSession ? 0.2 : 1,
51
+ replaysSessionSampleRate: touchSession ? 0.1 : 1,
31
52
  replaysOnErrorSampleRate: 1,
32
53
  dataCollection: { userInfo: true },
33
54
  initialScope: { tags: { game_slug: opts.slug } },
@@ -105,6 +126,7 @@ function initGameSentry(opts) {
105
126
  }
106
127
  var lastSnapshot = 0;
107
128
  function sentryCanvasSnapshot(canvas, opts) {
129
+ if (touchSession) return;
108
130
  const now = Date.now();
109
131
  if (now - lastSnapshot < (opts?.intervalMs ?? 500)) return;
110
132
  lastSnapshot = now;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genex-ai/embed-sdk",
3
- "version": "0.8.0",
4
- "description": "Player identity + durable game state for genex games — signed-in or guest play, per-player save slots, shared world state, and soft-trust leaderboards.",
3
+ "version": "0.10.0",
4
+ "description": "Player identity + durable game state for genex games \u2014 signed-in or guest play, per-player save slots, shared world state, and soft-trust leaderboards.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",