@cshah18/sdk 4.14.0 → 4.15.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.
@@ -837,6 +837,7 @@ class GroupListModal {
837
837
  this.onGroupMemberJoined = (event) => {
838
838
  const detail = event.detail || {};
839
839
  console.log("eventttttt", detail);
840
+ const eventType = event.type;
840
841
  const productId = detail.product_id;
841
842
  const groupData = detail.group;
842
843
  // Only process if this is for the current product
@@ -858,6 +859,16 @@ class GroupListModal {
858
859
  if (typeof participantsCount === "number") {
859
860
  this.groups[groupIndex].joined = participantsCount;
860
861
  }
862
+ // If this session left the currently joined group, clear stale local membership state.
863
+ const leavingSessionId = detail.session_id;
864
+ if (eventType === "group:member:left" &&
865
+ this.currentSessionId &&
866
+ leavingSessionId === this.currentSessionId &&
867
+ this.currentJoinedGroupId === groupId) {
868
+ this.currentJoinedGroupId = null;
869
+ this.groups[groupIndex].isMember = false;
870
+ this.updateStartButtonState(false, this.isLoading);
871
+ }
861
872
  this.logger.info(`[GroupListModal] Updated group ${groupId} - participants: ${participantsCount}`);
862
873
  // Re-render the specific group card
863
874
  this.updateGroupCard(groupId);
@@ -899,6 +910,7 @@ class GroupListModal {
899
910
  return;
900
911
  }
901
912
  window.addEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
913
+ window.addEventListener("group:member:left", this.handleGroupMemberJoinedEvent);
902
914
  this.socketListenerRegistered = true;
903
915
  this.logger.debug("[GroupListModal] Socket event listeners registered");
904
916
  }
@@ -908,6 +920,7 @@ class GroupListModal {
908
920
  return;
909
921
  }
910
922
  window.removeEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
923
+ window.removeEventListener("group:member:left", this.handleGroupMemberJoinedEvent);
911
924
  this.socketListenerRegistered = false;
912
925
  this.logger.debug("[GroupListModal] Socket event listeners unregistered");
913
926
  }
@@ -1039,13 +1052,11 @@ class GroupListModal {
1039
1052
  console.log("groupsss", groups);
1040
1053
  // If API reports membership, prefer it over cached state
1041
1054
  const memberGroup = groups.find((g) => g.isMember);
1042
- if (memberGroup) {
1043
- this.currentJoinedGroupId = memberGroup.groupId;
1044
- }
1055
+ this.currentJoinedGroupId = memberGroup ? memberGroup.groupId : null;
1045
1056
  this.groups = groups;
1046
1057
  this.liveCount = groups.length;
1047
1058
  this.logger.info(`Fetched ${groups.length} active groups`);
1048
- const hasMembership = Boolean(this.currentJoinedGroupId || groups.some((g) => g.isMember));
1059
+ const hasMembership = Boolean(memberGroup);
1049
1060
  this.updateStartButtonState(hasMembership, false);
1050
1061
  // Update and show live count display
1051
1062
  const liveCountText = document.getElementById("cobuy-live-count-text");
@@ -1403,7 +1414,7 @@ class GroupListModal {
1403
1414
  createGroupCard(group) {
1404
1415
  const card = document.createElement("div");
1405
1416
  card.className = "cobuy-group-card";
1406
- card.dataset.groupId = group.name || group.groupId;
1417
+ card.dataset.groupId = group.groupId;
1407
1418
  const header = document.createElement("div");
1408
1419
  header.className = "cobuy-group-card-header";
1409
1420
  const groupId = document.createElement("div");
@@ -2156,15 +2167,24 @@ function styleInject(css, ref) {
2156
2167
  }
2157
2168
  }
2158
2169
 
2159
- var css_248z = ".sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.cb-lobby-modal-container{height:100%;left:0;overflow-y:auto!important;position:fixed;top:0;width:100%;z-index:10000}.cb-lobby-modal-container *{box-sizing:border-box;margin:0;padding:0}.cb-lobby-modal-container body,.cb-lobby-modal-container html{font-family:Inter,sans-serif;height:100%;overflow-x:hidden;width:100%}.cb-lobby-main{backdrop-filter:blur(4px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:50px;box-shadow:0 25px 50px -12px #00000040;padding:80px;position:relative;width:100%;z-index:1}.cb-lobby-bg{background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/cb-back-image.png);background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:-1}.cb-lobby-main-wp{align-items:center;display:flex;justify-content:center;padding:40px 80px}.lobby-indicator{align-items:center;backdrop-filter:blur(8px);background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);border-radius:24px;display:flex;gap:12px;margin-bottom:16px;padding:6px 12px;width:fit-content}.lobby-indicator-logo{display:block;flex-shrink:0;height:34px;width:auto}.pulsing-dot{align-items:center;display:flex;height:8px;justify-content:center;position:relative;width:8px}.pulsing-dot:after{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.pulsing-dot:after,.pulsing-dot:before{background:#10b981;border-radius:50%;content:\"\";height:100%;position:absolute;width:100%}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.indicator-text{color:#047857;font-size:11px;font-weight:800;letter-spacing:.5px;line-height:1;text-transform:uppercase}.lobby-status-section{display:flex;flex-direction:column;margin-bottom:24px}.lobby-status{background:#fff;border-radius:4px;color:#000;font-size:10px;font-weight:600;line-height:1.2;padding:6px 10px;text-transform:uppercase}.lobby-status.active{background:#155dfc;color:#fff}.lobby-status.complete{background:#10b981;color:#fff}.lobby-status-wp{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.lobby-number{backdrop-filter:blur(3px);background:hsla(0,0%,100%,.2);border-radius:4px;box-shadow:0 25px 50px -12px #00000040;font-size:12px;font-weight:700;letter-spacing:1px;line-height:1.2;padding:5px 8px;text-transform:uppercase}.title-wp{margin-top:25px}.title-wp h2{font-size:60px;font-weight:900;line-height:1;margin-bottom:15px}.sub-title{-webkit-text-fill-color:transparent;animation:gradient-flow 4s linear infinite;background-clip:text;-webkit-background-clip:text;background-image:linear-gradient(90deg,#1e293b,#2563eb 25%,#1e293b 50%);background-size:200% auto;color:#1e293b;font-size:16px;font-weight:700;line-height:1.5;margin-bottom:0}@keyframes gradient-flow{0%{background-position:200% 0}to{background-position:-200% 0}}.sub-title.completed{-webkit-text-fill-color:unset;background-clip:unset;-webkit-background-clip:unset;background-image:none;color:#1e293b}.connected-section{backdrop-filter:blur(4px);background:hsla(0,0%,100%,.05);border:1px solid hsla(0,0%,100%,.1);border-radius:24px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex-direction:column;gap:20px;margin-top:24px;padding:24px;transition:all .3s ease}.connected-section:hover{background:hsla(0,0%,100%,.1);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.link-share-container{display:flex;flex-direction:column}.link-share-wrapper{align-items:center;display:flex;gap:12px;width:100%}.lobby-link-box{align-items:center;backdrop-filter:blur(8px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.3);border-radius:14px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex:1;gap:16px;justify-content:space-between;min-height:50px;padding:16px 20px;transition:all .2s ease}.lobby-link-box:hover{background-color:hsla(0,0%,100%,.5);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.lobby-link-text{color:#71717a;font-size:10px;font-weight:700;letter-spacing:.6px;line-height:1.2;margin-bottom:6px;text-transform:uppercase}.copy-link-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:#64748b;cursor:pointer;display:flex;flex-shrink:0;font-size:13px;font-weight:600;gap:6px;justify-content:center;padding:0;transition:all .2s ease;white-space:nowrap}.copy-link-btn:hover{color:#1e293b}.copy-link-btn .copy-text{display:none}.copy-link-btn svg{flex-shrink:0;height:18px;width:18px}@media (min-width:640px){.copy-link-btn .copy-text{display:inline}}.lobby-link-url{color:#1e293b;font-size:15px;font-weight:700;line-height:1.4;word-break:break-all}.link-box-container{flex:1;min-width:0}.share-btn{align-items:center;background:#1e293b;border:none;border-radius:14px;box-shadow:0 2px 4px 0 rgba(0,0,0,.1);color:#fff;cursor:pointer;display:flex;flex-shrink:0;font-size:15px;font-weight:600;gap:8px;height:50px;justify-content:center;min-width:auto;padding:35px 24px;transition:all .2s ease}.share-btn .share-text{color:#fff;display:inline;font-size:18px;margin:0!important}.share-btn svg{color:#fff;flex-shrink:0;height:18px;width:18px}@media (max-width:640px){.share-btn{font-size:14px;height:50px;padding:0 18px}.share-btn .share-text{color:#fff;display:inline}}.share-btn:hover{background:#0f172a;box-shadow:0 4px 8px 0 rgba(0,0,0,.15);transform:translateY(-1px)}.share-btn:active{transform:scale(.95)}.lobby-offer-box{align-items:center;backdrop-filter:blur(8px);background-color:rgba(59,130,246,.063);border:1px solid rgba(59,130,246,.125);border-radius:1rem;display:flex;gap:30px;margin-top:30px;padding:15px 20px}.offer-box-icon{align-items:center;background:linear-gradient(135deg,#3b82f6,rgba(59,130,246,.867));border-radius:10px;box-shadow:0 10px 15px -3px rgba(59,130,246,.314);color:#fff;cursor:pointer;display:flex;height:56px;justify-content:center;padding:5px;width:56px}.offer-lock-status{align-items:center;display:flex;gap:6px;margin-bottom:2px}.offer-lock-status span{font-size:14px;font-weight:700;line-height:1}.offer-box-content h3{font-size:30px;font-weight:900;line-height:1.2}.cb-lobby-top{display:grid;gap:100px;grid-template-columns:7fr 5fr}.group-info{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:20px;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.progress-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:14px}.progress-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;justify-content:center;letter-spacing:.7px;position:relative}.progress-badge{background:#3b82f6;border-radius:999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;padding:6px 12px}.cb-lobby-modal-container .progress-bar{background:#fff;border-radius:999px;height:14px;overflow:hidden;width:100%}.cb-lobby-modal-container .progress-fill{animation:shimmer 2.7s linear infinite;background:#3b82f6;background-image:linear-gradient(120deg,hsla(0,0%,100%,.15) 25%,hsla(0,0%,100%,.45) 37%,hsla(0,0%,100%,.15) 63%);background-size:200% 100%;border-radius:999px;height:100%;position:relative;transition:width .6s ease;width:0}@keyframes shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.progress-labels{color:#474d56;display:flex;font-size:12px;justify-content:space-between;margin-top:8px}.team-card{margin-top:40px}.team-card .icon-box svg{width:16px}.team-card .team-header{align-items:center;display:flex;justify-content:space-between}.team-card .team-title{align-items:center;display:flex;font-size:14px;font-weight:600;gap:12px;letter-spacing:1px}.team-card .icon-box{align-items:center;background-color:#3b82f6;border-radius:.5rem;box-shadow:0 10px 15px -3px #3b82f64d;color:#fff;display:flex;font-size:18px;height:2rem;justify-content:center;width:2rem}.team-card .team-title span{color:#000;font-size:14px;font-weight:900;letter-spacing:.7px}.team-card .team-count{background-color:#3b82f6;border-radius:9999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;letter-spacing:.7px;padding:6px 12px}.team-card .team-members{align-items:center;display:flex;gap:14px;justify-content:center;margin-top:20px}.team-card .member{border:3px solid #fff;border-radius:50%;box-shadow:0 4px 12px #0000001a;font-size:22px;height:56px;position:relative;width:56px}.team-card .member,.team-card .member:after{align-items:center;color:#fff;display:flex;justify-content:center}.team-card .member:after{background:#3b82f6;background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/tick-mark-icon.svg);background-position:50%;background-repeat:no-repeat;background-size:75%;border:2px solid #fff;border-radius:50%;bottom:-4px;content:\"\";height:12px;padding:2px;position:absolute;right:-4px;width:12px}.team-card .member.blue{background:#2563eb}.team-card .member.purple{background:#9333ea}.team-card .member.pink{background:#ec4899}.team-card .member.orange{background:#f97316}.team-card .member.empty{background:#f8fafc;color:#9ca3af}.team-card .member.empty:after{display:none}.time-card{align-items:center;backdrop-filter:blur(8px);background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.2);border-radius:20px;display:flex;gap:14px;margin-top:30px;padding:15px}.time-card .time-icon{align-items:center;background:#3b82f6;border-radius:14px;color:#fff;display:flex;flex-shrink:0;font-size:22px;height:48px;justify-content:center;width:48px}.time-card .time-content{display:flex;flex-direction:column}.time-card .time-label{color:#4b5563;font-size:12px;font-weight:600;letter-spacing:.6px}.time-card .time-value{color:#111827;font-size:22px;font-weight:900}.group-info-box{backdrop-filter:blur(8px);background:color-mix(in oklab,#fff 5%,transparent);border:1px solid color-mix(in oklab,#fff 20%,transparent);border-radius:1.5rem;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.offer-lock-status svg{height:16px;width:16px}.cb-lobby-modal-container .offer-box-content .reward-text{background:unset;border:none;color:#000;font-size:14px;line-height:1;margin-top:4px}.progress-header .title:before{background:#3b82f6;border-radius:50%;content:\"\";display:inline-block;height:8px;margin-right:8px;position:relative;width:8px}.live-activity-wrapper{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 80%,transparent);border-radius:18px;box-shadow:0 30px 80px rgba(0,0,0,.15);padding:16px}.live-activity-header{display:flex;gap:10px;justify-content:space-between;margin-bottom:12px}.live-activity-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;gap:8px;letter-spacing:.7px}.live-activity-header .dot{background:#3b82f6;border-radius:50%;height:8px;position:relative;width:8px}.live-activity-header .dot:after{animation:livePulse 1.6s ease-out infinite;background:rgba(59,130,246,.6);border-radius:50%;content:\"\";inset:0;position:absolute}@keyframes livePulse{0%{opacity:.8;transform:scale(1)}70%{opacity:0;transform:scale(2.4)}to{opacity:0}}.activity-stats{align-items:center;display:flex;gap:8px}.activity-stats-badge{background-color:rgba(59,130,246,.082);border-radius:999px;color:#3b82f6;font-size:12px;font-weight:500;line-height:1;padding:4px 10px}.activity-stats-badge.light{background-color:#f9f3f4;color:#45556c}.activity-list{height:104px;overflow:hidden;position:relative}.activity-card{align-items:center;background:linear-gradient(90deg,#fff,#f2f6ff);border:1px solid #dbeafe;border-radius:14px;display:flex;gap:10px;height:58px;inset:0;padding:12px 10px;position:absolute;transition:transform .6s cubic-bezier(.22,.61,.36,1),opacity .6s}.activity-card .text{font-size:14px}.activity-card .avatar{align-items:center;border-radius:50%;color:#fff;display:flex;flex:0 0 30px;height:30px;justify-content:center;width:30px}.activity-card .pink{background:linear-gradient(135deg,#ec4899,#f472b6)}.activity-card .purple{background:linear-gradient(135deg,#8b5cf6,#a78bfa)}.activity-card .blue{background:linear-gradient(135deg,#3b82f6,#60a5fa)}.activity-card .green{background:linear-gradient(135deg,#10b981,#34d399)}.activity-card .orange{background:linear-gradient(135deg,#f97316,#fb923c)}.activity-card .text p{color:#6b7280;font-size:12px;margin:2px 0 0}.activity-card .time{color:#94a3b8;font-size:10px;margin-left:auto}.lobby-activity-wp{backdrop-filter:blur(12px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.2);border-radius:25px;box-shadow:0 1px 3px 0 #0000001a;margin-top:50px;padding:8px}.lobby-close-icon{align-items:center;background-color:color-mix(in oklab,#000 80%,transparent);border-radius:50%;color:#fff;cursor:pointer;display:flex;height:34px;justify-content:center;position:fixed;right:30px;top:30px;width:34px;z-index:99}.lobby-close-icon svg{height:20px;width:20px}@media screen and (max-width:1600px){.cb-lobby-main{border-radius:30px;padding:50px}.title-wp h2{font-size:48px;margin-bottom:12px}.sub-title{font-size:16px}.title-wp{margin-top:20px}.lobby-link-section{margin-top:30px}.offer-box-content h3{font-size:26px}.lobby-offer-box{gap:24px;padding:15px}.cb-lobby-top{gap:80px}.group-info-box{border-radius:20px;padding:22px}.team-card{margin-top:30px}.team-card .team-members{margin-top:12px}.lobby-activity-wp{margin-top:40px}.lobby-close-icon{height:30px;top:20px;width:30px}}@media screen and (max-width:1280px){.cb-lobby-main,.cb-lobby-main-wp{padding:40px}.title-wp h2{font-size:42px}.cb-lobby-top{gap:60px}}@media screen and (max-width:1120px){.title-wp h2{font-size:38px}}@media screen and (max-width:991px){.cb-lobby-top{gap:30px;grid-template-columns:1fr}.cb-lobby-main{border-radius:15px;padding:30px}.cb-lobby-main-wp{padding:30px}.lobby-close-icon{right:20px}.lobby-link-box{border-radius:10px}.share-btn{border-radius:8px}.offer-box-content h3{font-size:24px}.lobby-activity-wp,.lobby-offer-box,.time-card{border-radius:15px;margin-top:20px}.live-activity-wrapper{border-radius:10px}.group-info-box{border-radius:15px}}@media screen and (max-width:767px){.link-share-wrapper{flex-direction:column!important}}@media screen and (max-width:575px){.cb-lobby-main,.cb-lobby-main-wp{padding:20px}.title-wp h2{font-size:30px}.lobby-link-text{font-size:11px;margin-bottom:4px}.lobby-link-url{font-size:14px}.lobby-link-box{min-height:48px;padding:12px 16px}.link-share-wrapper{gap:10px}.share-btn{border-radius:8px;font-size:13px;height:48px;min-width:auto;padding:0 14px}.lobby-offer-box{padding:10px}.offer-box-content h3{font-size:20px}.offer-box-content .reward-text{font-size:13px}.group-info-box{padding:13px}.progress-header .title,.team-card .team-title span{font-size:13px}.team-card .member{height:40px;width:40px}.team-card .member:after{height:8px;width:8px}.team-card .team-members{gap:10px;margin-top:12px}.time-card{padding:13px}.team-card{margin-top:22px}.activity-card .text{font-size:12px}.activity-card .text p{font-size:11px}.live-activity-header .title{font-size:12px}.time-card .time-value{font-size:20px}}@media screen and (max-width:480px){.cb-lobby-main-wp{padding:20px 12px}.cb-lobby-main{padding:15px 12px}.lobby-status{font-size:9px}.lobby-number{font-size:10px}.title-wp h2{font-size:28px;margin-bottom:7px}.sub-title{font-size:14px}.lobby-link-section{gap:10px;margin-top:20px}.lobby-offer-box{gap:12px}.offer-box-icon{height:45px;width:45px}.offer-box-content .reward-text,.offer-lock-status span{font-size:12px}.cb-lobby-top{gap:20px}.lobby-close-icon{right:10px;top:10px}.live-activity-header{flex-direction:column;gap:5px}.activity-card{border-radius:9px;padding:6px}}.share-overlay{align-items:center;background:rgba(0,0,0,.45);display:flex;inset:0;justify-content:center;opacity:0;position:fixed;transition:.3s ease;visibility:hidden;z-index:999}.share-overlay.active{opacity:1;visibility:visible}.share-popup{background:#fff;border-radius:18px;max-height:90%;overflow:auto;padding:24px;position:relative;transform:translateY(30px);transition:.35s ease;width:420px}.share-overlay.active .share-popup{transform:translateY(0)}.share-popup .close-btn{align-items:center;background:#f3f4f6;border:none;border-radius:50%;cursor:pointer;display:flex;height:32px;justify-content:center;position:absolute;right:14px;top:14px;transition:.3s;width:32px}.share-popup .close-btn:hover{background:#eeecec}.share-popup h2{font-size:22px;font-weight:600;line-height:1;margin-bottom:10px}.share-popup .subtitle{color:#4a5565;font-size:14px;margin:6px 0 30px}.share-popup .share-grid{display:grid;gap:14px;grid-template-columns:repeat(2,1fr)}.share-popup .share-card{align-items:center;background:#f2f6f9;border-radius:14px;cursor:pointer;display:flex;flex-direction:column;padding:16px;position:relative;text-align:center;transition:.25s ease}.share-popup .share-card:hover{transform:translateY(-3px)}.share-popup .share-card .icon{align-items:center;background:#1877f2;border-radius:50%;color:#fff;display:flex;font-size:30px;height:50px;justify-content:center;margin-bottom:8px;text-align:center;width:50px}.share-popup .share-card span{font-size:13px;font-weight:500}.share-popup .share-card.whatsapp{background:#ecfdf5;border:2px solid #22c55e;color:#22c55e}.share-popup .share-card.whatsapp .icon{background:#22c55e}.share-popup .share-card .badge{background:#2563eb;border-radius:12px;color:#fff;font-size:11px;padding:4px 10px;position:absolute;right:10px;top:-8px}.share-popup .link-box{background:#fbf9fa;border:1px solid #ebe6e7;border-radius:14px;margin-top:18px;padding:14px}.share-popup .link-box label{color:#64748b;font-size:13px}.share-popup .link-row{display:flex;gap:8px;margin-top:6px}.share-popup .link-row input{background:transparent;border:none;color:#334155;flex:1;font-size:13px;letter-spacing:.8px;line-height:1.2;outline:none}.share-popup .link-row button{align-items:center;background:#fff;border:1px solid #ebe6e7;border-radius:10px;cursor:pointer;display:flex;height:35px;justify-content:center;padding:6px 8px;width:35px}.share-popup .success{color:#2563eb;display:block;font-size:12px;margin-top:6px;opacity:0;transition:opacity .3s}.share-popup .success.show{opacity:1}.share-popup .footer-text{color:#64748b;font-size:12px;margin-top:14px;text-align:center}.share-popup .share-card.twitter .icon{background:#000}.share-popup .share-card.facebook .icon{background:#1877f2}.share-popup .share-card.sms .icon{background:#059669}.share-popup .share-card.copied .icon{background:#2563eb}@keyframes entrance-fade-in{0%{opacity:0}to{opacity:1}}@keyframes entrance-scale-in{0%{opacity:0;transform:scale(.9) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes entrance-text-fade-in{0%{opacity:0}to{opacity:1}}@keyframes exit-blur-scale{0%{filter:blur(0);opacity:1;transform:scale(1)}to{filter:blur(10px);opacity:0;transform:scale(1.1)}}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.5}}.entrance-animation-overlay{align-items:center;animation:entrance-fade-in .8s ease-in-out forwards;background-color:#0f172a;color:#fff;display:flex;flex-direction:column;inset:0;justify-content:center;position:fixed;z-index:9999}.entrance-animation-overlay.exit{animation:exit-blur-scale .8s ease-in-out forwards}.entrance-content{align-items:center;animation:entrance-scale-in .5s ease-out .2s both;display:flex;flex-direction:column}.entrance-icon-box{align-items:center;background:#2563eb;border-radius:16px;box-shadow:0 10px 25px rgba(37,99,235,.2);display:flex;height:64px;justify-content:center;margin-bottom:24px;width:64px}.entrance-icon-box svg{color:#fff;height:32px;width:32px}.entrance-title{font-size:32px;font-weight:700;letter-spacing:-.5px;margin-bottom:12px}@media (min-width:768px){.entrance-title{font-size:48px}}.entrance-message{align-items:center;animation:entrance-text-fade-in .5s ease-out .5s both;color:#60a5fa;display:flex;font-size:18px;font-weight:500;gap:8px}.entrance-pulse-dot{animation:pulse-dot 1.5s ease-in-out infinite;background-color:#60a5fa;border-radius:50%;height:8px;width:8px}";
2170
+ var css_248z = ".sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.cb-lobby-modal-container{height:100%;left:0;overflow-y:auto!important;position:fixed;top:0;width:100%;z-index:10000}.cb-lobby-modal-container *{box-sizing:border-box;margin:0;padding:0}.cb-lobby-modal-container body,.cb-lobby-modal-container html{font-family:Inter,sans-serif;height:100%;overflow-x:hidden;width:100%}.cb-lobby-main{backdrop-filter:blur(4px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:50px;box-shadow:0 25px 50px -12px #00000040;padding:80px;position:relative;width:100%;z-index:1}.cb-lobby-bg{background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/cb-back-image.png);background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:-1}.cb-lobby-main-wp{align-items:center;display:flex;justify-content:center;padding:40px 80px}.lobby-indicator{align-items:center;backdrop-filter:blur(8px);background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);border-radius:24px;display:flex;gap:12px;margin-bottom:16px;padding:6px 12px;width:fit-content}.lobby-indicator-logo{display:block;flex-shrink:0;height:34px;width:auto}.pulsing-dot{align-items:center;display:flex;height:8px;justify-content:center;position:relative;width:8px}.pulsing-dot:after{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.pulsing-dot:after,.pulsing-dot:before{background:#10b981;border-radius:50%;content:\"\";height:100%;position:absolute;width:100%}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.indicator-text{color:#047857;font-size:11px;font-weight:800;letter-spacing:.5px;line-height:1;text-transform:uppercase}.lobby-status-section{display:flex;flex-direction:column;margin-bottom:24px}.lobby-status{background:#fff;border-radius:4px;color:#000;font-size:10px;font-weight:600;line-height:1.2;padding:6px 10px;text-transform:uppercase}.lobby-status.active{background:#155dfc;color:#fff}.lobby-status.complete{background:#10b981;color:#fff}.lobby-status-wp{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.lobby-number{backdrop-filter:blur(3px);background:hsla(0,0%,100%,.2);border-radius:4px;box-shadow:0 25px 50px -12px #00000040;font-size:12px;font-weight:700;letter-spacing:1px;line-height:1.2;padding:5px 8px;text-transform:uppercase}.title-wp{margin-top:25px}.title-wp h2{font-size:60px;font-weight:900;line-height:1;margin-bottom:15px}.sub-title{-webkit-text-fill-color:transparent;animation:gradient-flow 4s linear infinite;background-clip:text;-webkit-background-clip:text;background-image:linear-gradient(90deg,#1e293b,#2563eb 25%,#1e293b 50%);background-size:200% auto;color:#1e293b;font-size:16px;font-weight:700;line-height:1.5;margin-bottom:0}@keyframes gradient-flow{0%{background-position:200% 0}to{background-position:-200% 0}}.sub-title.completed{-webkit-text-fill-color:unset;background-clip:unset;-webkit-background-clip:unset;background-image:none;color:#1e293b}.connected-section{backdrop-filter:blur(4px);background:hsla(0,0%,100%,.05);border:1px solid hsla(0,0%,100%,.1);border-radius:24px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex-direction:column;gap:20px;margin-top:24px;padding:24px;transition:all .3s ease}.connected-section:hover{background:hsla(0,0%,100%,.1);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.lobby-contact-protection-banner{align-items:center;background:#fffbeb;border:1px solid #f59e0b;border-radius:12px;display:flex;gap:12px;justify-content:space-between;padding:12px 14px}.lobby-contact-protection-copy{color:#7c2d12;font-size:13px;font-weight:600;line-height:1.4}.lobby-contact-protection-btn{background:#1e293b;border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:12px;font-weight:700;padding:8px 12px;white-space:nowrap}.lobby-contact-protection-btn:hover{background:#0f172a}.lobby-protected-leave-container{align-items:center;background:#f0fdf4;border:1px solid #86efac;border-radius:12px;display:none;gap:12px;justify-content:space-between;padding:12px 14px}.lobby-protected-leave-copy{color:#166534;font-size:13px;font-weight:600;line-height:1.4}.lobby-protected-leave-btn{background:#b91c1c;border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:12px;font-weight:700;padding:8px 12px;white-space:nowrap}.lobby-protected-leave-btn:hover{background:#991b1b}.lobby-contact-overlay{align-items:center;background:rgba(2,6,23,.5);display:flex;inset:0;justify-content:center;padding:20px;position:fixed;z-index:10020}.lobby-contact-card{background:#fff;border-radius:16px;box-shadow:0 12px 32px rgba(15,23,42,.25);display:flex;flex-direction:column;gap:12px;padding:20px;width:min(460px,100%)}.lobby-contact-title{color:#0f172a;font-size:22px;font-weight:800;line-height:1.2}.lobby-contact-subtitle{color:#334155;font-size:14px;line-height:1.5}.lobby-contact-input{border:1px solid #cbd5e1;border-radius:10px;font-size:14px;outline:none;padding:11px 12px;width:100%}.lobby-contact-input:focus{border-color:#1d4ed8;box-shadow:0 0 0 3px rgba(59,130,246,.2)}.lobby-contact-actions{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.lobby-contact-danger,.lobby-contact-primary,.lobby-contact-secondary{border:none;border-radius:10px;cursor:pointer;font-size:13px;font-weight:700;padding:10px 14px}.lobby-contact-danger:disabled,.lobby-contact-primary:disabled,.lobby-contact-secondary:disabled{cursor:not-allowed;opacity:.7}.lobby-contact-primary.is-loading{padding-right:34px;position:relative}.lobby-contact-primary.is-loading:after{animation:lobby-spin .8s linear infinite;border:2px solid hsla(0,0%,100%,.45);border-radius:50%;border-top-color:#fff;content:\"\";height:14px;margin-top:-7px;position:absolute;right:12px;top:50%;width:14px}@keyframes lobby-spin{to{transform:rotate(1turn)}}.lobby-contact-primary{background:#1d4ed8;color:#fff}.lobby-contact-primary:hover{background:#1e40af}.lobby-contact-secondary{background:#e2e8f0;color:#1e293b}.lobby-contact-secondary:hover{background:#cbd5e1}.lobby-contact-danger{background:#fee2e2;color:#991b1b}.lobby-contact-danger:hover{background:#fecaca}.lobby-contact-toast{background:#0f172a;border-radius:10px;bottom:30px;box-shadow:0 8px 20px rgba(0,0,0,.25);color:#fff;font-size:13px;font-weight:600;left:50%;padding:10px 14px;position:fixed;transform:translateX(-50%);z-index:10030}.lobby-blocking-loader-overlay{align-items:center;background:rgba(2,6,23,.45);display:flex;inset:0;justify-content:center;padding:20px;position:fixed;z-index:10035}.lobby-blocking-loader-card{align-items:center;background:#fff;border-radius:12px;box-shadow:0 12px 30px rgba(15,23,42,.24);display:flex;gap:10px;padding:16px 18px}.lobby-blocking-loader-spinner{animation:lobby-spin .8s linear infinite;border:2px solid #cbd5e1;border-radius:50%;border-top-color:#1d4ed8;height:16px;width:16px}.lobby-blocking-loader-text{color:#0f172a;font-size:14px;font-weight:600;margin:0}.link-share-container{display:flex;flex-direction:column}.link-share-wrapper{align-items:center;display:flex;gap:12px;width:100%}.lobby-link-box{align-items:center;backdrop-filter:blur(8px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.3);border-radius:14px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex:1;gap:16px;justify-content:space-between;min-height:50px;padding:16px 20px;transition:all .2s ease}.lobby-link-box:hover{background-color:hsla(0,0%,100%,.5);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.lobby-link-text{color:#71717a;font-size:10px;font-weight:700;letter-spacing:.6px;line-height:1.2;margin-bottom:6px;text-transform:uppercase}.copy-link-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:#64748b;cursor:pointer;display:flex;flex-shrink:0;font-size:13px;font-weight:600;gap:6px;justify-content:center;padding:0;transition:all .2s ease;white-space:nowrap}.copy-link-btn:hover{color:#1e293b}.copy-link-btn .copy-text{display:none}.copy-link-btn svg{flex-shrink:0;height:18px;width:18px}@media (min-width:640px){.copy-link-btn .copy-text{display:inline}}@media (max-width:640px){.lobby-contact-protection-banner{align-items:flex-start;flex-direction:column}.lobby-contact-protection-btn{width:100%}.lobby-protected-leave-container{align-items:flex-start;flex-direction:column}.lobby-protected-leave-btn{width:100%}}.lobby-link-url{color:#1e293b;font-size:15px;font-weight:700;line-height:1.4;word-break:break-all}.link-box-container{flex:1;min-width:0}.share-btn{align-items:center;background:#1e293b;border:none;border-radius:14px;box-shadow:0 2px 4px 0 rgba(0,0,0,.1);color:#fff;cursor:pointer;display:flex;flex-shrink:0;font-size:15px;font-weight:600;gap:8px;height:50px;justify-content:center;min-width:auto;padding:35px 24px;transition:all .2s ease}.share-btn .share-text{color:#fff;display:inline;font-size:18px;margin:0!important}.share-btn svg{color:#fff;flex-shrink:0;height:18px;width:18px}@media (max-width:640px){.share-btn{font-size:14px;height:50px;padding:0 18px}.share-btn .share-text{color:#fff;display:inline}}.share-btn:hover{background:#0f172a;box-shadow:0 4px 8px 0 rgba(0,0,0,.15);transform:translateY(-1px)}.share-btn:active{transform:scale(.95)}.lobby-offer-box{align-items:center;backdrop-filter:blur(8px);background-color:rgba(59,130,246,.063);border:1px solid rgba(59,130,246,.125);border-radius:1rem;display:flex;gap:30px;margin-top:30px;padding:15px 20px}.offer-box-icon{align-items:center;background:linear-gradient(135deg,#3b82f6,rgba(59,130,246,.867));border-radius:10px;box-shadow:0 10px 15px -3px rgba(59,130,246,.314);color:#fff;cursor:pointer;display:flex;height:56px;justify-content:center;padding:5px;width:56px}.offer-lock-status{align-items:center;display:flex;gap:6px;margin-bottom:2px}.offer-lock-status span{font-size:14px;font-weight:700;line-height:1}.offer-box-content h3{font-size:30px;font-weight:900;line-height:1.2}.cb-lobby-top{display:grid;gap:100px;grid-template-columns:7fr 5fr}.group-info{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:20px;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.progress-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:14px}.progress-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;justify-content:center;letter-spacing:.7px;position:relative}.progress-badge{background:#3b82f6;border-radius:999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;padding:6px 12px}.cb-lobby-modal-container .progress-bar{background:#fff;border-radius:999px;height:14px;overflow:hidden;width:100%}.cb-lobby-modal-container .progress-fill{animation:shimmer 2.7s linear infinite;background:#3b82f6;background-image:linear-gradient(120deg,hsla(0,0%,100%,.15) 25%,hsla(0,0%,100%,.45) 37%,hsla(0,0%,100%,.15) 63%);background-size:200% 100%;border-radius:999px;height:100%;position:relative;transition:width .6s ease;width:0}@keyframes shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.progress-labels{color:#474d56;display:flex;font-size:12px;justify-content:space-between;margin-top:8px}.team-card{margin-top:40px}.team-card .icon-box svg{width:16px}.team-card .team-header{align-items:center;display:flex;justify-content:space-between}.team-card .team-title{align-items:center;display:flex;font-size:14px;font-weight:600;gap:12px;letter-spacing:1px}.team-card .icon-box{align-items:center;background-color:#3b82f6;border-radius:.5rem;box-shadow:0 10px 15px -3px #3b82f64d;color:#fff;display:flex;font-size:18px;height:2rem;justify-content:center;width:2rem}.team-card .team-title span{color:#000;font-size:14px;font-weight:900;letter-spacing:.7px}.team-card .team-count{background-color:#3b82f6;border-radius:9999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;letter-spacing:.7px;padding:6px 12px}.team-card .team-members{align-items:center;display:flex;gap:14px;justify-content:center;margin-top:20px}.team-card .member{border:3px solid #fff;border-radius:50%;box-shadow:0 4px 12px #0000001a;font-size:22px;height:56px;position:relative;width:56px}.team-card .member,.team-card .member:after{align-items:center;color:#fff;display:flex;justify-content:center}.team-card .member:after{background:#3b82f6;background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/tick-mark-icon.svg);background-position:50%;background-repeat:no-repeat;background-size:75%;border:2px solid #fff;border-radius:50%;bottom:-4px;content:\"\";height:12px;padding:2px;position:absolute;right:-4px;width:12px}.team-card .member.blue{background:#2563eb}.team-card .member.purple{background:#9333ea}.team-card .member.pink{background:#ec4899}.team-card .member.orange{background:#f97316}.team-card .member.empty{background:#f8fafc;color:#9ca3af}.team-card .member.empty:after{display:none}.time-card{align-items:center;backdrop-filter:blur(8px);background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.2);border-radius:20px;display:flex;gap:14px;margin-top:30px;padding:15px}.time-card .time-icon{align-items:center;background:#3b82f6;border-radius:14px;color:#fff;display:flex;flex-shrink:0;font-size:22px;height:48px;justify-content:center;width:48px}.time-card .time-content{display:flex;flex-direction:column}.time-card .time-label{color:#4b5563;font-size:12px;font-weight:600;letter-spacing:.6px}.time-card .time-value{color:#111827;font-size:22px;font-weight:900}.group-info-box{backdrop-filter:blur(8px);background:color-mix(in oklab,#fff 5%,transparent);border:1px solid color-mix(in oklab,#fff 20%,transparent);border-radius:1.5rem;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.offer-lock-status svg{height:16px;width:16px}.cb-lobby-modal-container .offer-box-content .reward-text{background:unset;border:none;color:#000;font-size:14px;line-height:1;margin-top:4px}.progress-header .title:before{background:#3b82f6;border-radius:50%;content:\"\";display:inline-block;height:8px;margin-right:8px;position:relative;width:8px}.live-activity-wrapper{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 80%,transparent);border-radius:18px;box-shadow:0 30px 80px rgba(0,0,0,.15);padding:16px}.live-activity-header{display:flex;gap:10px;justify-content:space-between;margin-bottom:12px}.live-activity-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;gap:8px;letter-spacing:.7px}.live-activity-header .dot{background:#3b82f6;border-radius:50%;height:8px;position:relative;width:8px}.live-activity-header .dot:after{animation:livePulse 1.6s ease-out infinite;background:rgba(59,130,246,.6);border-radius:50%;content:\"\";inset:0;position:absolute}@keyframes livePulse{0%{opacity:.8;transform:scale(1)}70%{opacity:0;transform:scale(2.4)}to{opacity:0}}.activity-stats{align-items:center;display:flex;gap:8px}.activity-stats-badge{background-color:rgba(59,130,246,.082);border-radius:999px;color:#3b82f6;font-size:12px;font-weight:500;line-height:1;padding:4px 10px}.activity-stats-badge.light{background-color:#f9f3f4;color:#45556c}.activity-list{height:104px;overflow:hidden;position:relative}.activity-card{align-items:center;background:linear-gradient(90deg,#fff,#f2f6ff);border:1px solid #dbeafe;border-radius:14px;display:flex;gap:10px;height:58px;inset:0;padding:12px 10px;position:absolute;transition:transform .6s cubic-bezier(.22,.61,.36,1),opacity .6s}.activity-card .text{font-size:14px}.activity-card .avatar{align-items:center;border-radius:50%;color:#fff;display:flex;flex:0 0 30px;height:30px;justify-content:center;width:30px}.activity-card .pink{background:linear-gradient(135deg,#ec4899,#f472b6)}.activity-card .purple{background:linear-gradient(135deg,#8b5cf6,#a78bfa)}.activity-card .blue{background:linear-gradient(135deg,#3b82f6,#60a5fa)}.activity-card .green{background:linear-gradient(135deg,#10b981,#34d399)}.activity-card .orange{background:linear-gradient(135deg,#f97316,#fb923c)}.activity-card .text p{color:#6b7280;font-size:12px;margin:2px 0 0}.activity-card .time{color:#94a3b8;font-size:10px;margin-left:auto}.lobby-activity-wp{backdrop-filter:blur(12px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.2);border-radius:25px;box-shadow:0 1px 3px 0 #0000001a;margin-top:50px;padding:8px}.lobby-close-icon{align-items:center;background-color:color-mix(in oklab,#000 80%,transparent);border-radius:50%;color:#fff;cursor:pointer;display:flex;height:34px;justify-content:center;position:fixed;right:30px;top:30px;width:34px;z-index:99}.lobby-close-icon svg{height:20px;width:20px}@media screen and (max-width:1600px){.cb-lobby-main{border-radius:30px;padding:50px}.title-wp h2{font-size:48px;margin-bottom:12px}.sub-title{font-size:16px}.title-wp{margin-top:20px}.lobby-link-section{margin-top:30px}.offer-box-content h3{font-size:26px}.lobby-offer-box{gap:24px;padding:15px}.cb-lobby-top{gap:80px}.group-info-box{border-radius:20px;padding:22px}.team-card{margin-top:30px}.team-card .team-members{margin-top:12px}.lobby-activity-wp{margin-top:40px}.lobby-close-icon{height:30px;top:20px;width:30px}}@media screen and (max-width:1280px){.cb-lobby-main,.cb-lobby-main-wp{padding:40px}.title-wp h2{font-size:42px}.cb-lobby-top{gap:60px}}@media screen and (max-width:1120px){.title-wp h2{font-size:38px}}@media screen and (max-width:991px){.cb-lobby-top{gap:30px;grid-template-columns:1fr}.cb-lobby-main{border-radius:15px;padding:30px}.cb-lobby-main-wp{padding:30px}.lobby-close-icon{right:20px}.lobby-link-box{border-radius:10px}.share-btn{border-radius:8px}.offer-box-content h3{font-size:24px}.lobby-activity-wp,.lobby-offer-box,.time-card{border-radius:15px;margin-top:20px}.live-activity-wrapper{border-radius:10px}.group-info-box{border-radius:15px}}@media screen and (max-width:767px){.link-share-wrapper{flex-direction:column!important}}@media screen and (max-width:575px){.cb-lobby-main,.cb-lobby-main-wp{padding:20px}.title-wp h2{font-size:30px}.lobby-link-text{font-size:11px;margin-bottom:4px}.lobby-link-url{font-size:14px}.lobby-link-box{min-height:48px;padding:12px 16px}.link-share-wrapper{gap:10px}.share-btn{border-radius:8px;font-size:13px;height:48px;min-width:auto;padding:0 14px}.lobby-offer-box{padding:10px}.offer-box-content h3{font-size:20px}.offer-box-content .reward-text{font-size:13px}.group-info-box{padding:13px}.progress-header .title,.team-card .team-title span{font-size:13px}.team-card .member{height:40px;width:40px}.team-card .member:after{height:8px;width:8px}.team-card .team-members{gap:10px;margin-top:12px}.time-card{padding:13px}.team-card{margin-top:22px}.activity-card .text{font-size:12px}.activity-card .text p{font-size:11px}.live-activity-header .title{font-size:12px}.time-card .time-value{font-size:20px}}@media screen and (max-width:480px){.cb-lobby-main-wp{padding:20px 12px}.cb-lobby-main{padding:15px 12px}.lobby-status{font-size:9px}.lobby-number{font-size:10px}.title-wp h2{font-size:28px;margin-bottom:7px}.sub-title{font-size:14px}.lobby-link-section{gap:10px;margin-top:20px}.lobby-offer-box{gap:12px}.offer-box-icon{height:45px;width:45px}.offer-box-content .reward-text,.offer-lock-status span{font-size:12px}.cb-lobby-top{gap:20px}.lobby-close-icon{right:10px;top:10px}.live-activity-header{flex-direction:column;gap:5px}.activity-card{border-radius:9px;padding:6px}}.share-overlay{align-items:center;background:rgba(0,0,0,.45);display:flex;inset:0;justify-content:center;opacity:0;position:fixed;transition:.3s ease;visibility:hidden;z-index:999}.share-overlay.active{opacity:1;visibility:visible}.share-popup{background:#fff;border-radius:18px;max-height:90%;overflow:auto;padding:24px;position:relative;transform:translateY(30px);transition:.35s ease;width:420px}.share-overlay.active .share-popup{transform:translateY(0)}.share-popup .close-btn{align-items:center;background:#f3f4f6;border:none;border-radius:50%;cursor:pointer;display:flex;height:32px;justify-content:center;position:absolute;right:14px;top:14px;transition:.3s;width:32px}.share-popup .close-btn:hover{background:#eeecec}.share-popup h2{font-size:22px;font-weight:600;line-height:1;margin-bottom:10px}.share-popup .subtitle{color:#4a5565;font-size:14px;margin:6px 0 30px}.share-popup .share-grid{display:grid;gap:14px;grid-template-columns:repeat(2,1fr)}.share-popup .share-card{align-items:center;background:#f2f6f9;border-radius:14px;cursor:pointer;display:flex;flex-direction:column;padding:16px;position:relative;text-align:center;transition:.25s ease}.share-popup .share-card:hover{transform:translateY(-3px)}.share-popup .share-card .icon{align-items:center;background:#1877f2;border-radius:50%;color:#fff;display:flex;font-size:30px;height:50px;justify-content:center;margin-bottom:8px;text-align:center;width:50px}.share-popup .share-card span{font-size:13px;font-weight:500}.share-popup .share-card.whatsapp{background:#ecfdf5;border:2px solid #22c55e;color:#22c55e}.share-popup .share-card.whatsapp .icon{background:#22c55e}.share-popup .share-card .badge{background:#2563eb;border-radius:12px;color:#fff;font-size:11px;padding:4px 10px;position:absolute;right:10px;top:-8px}.share-popup .link-box{background:#fbf9fa;border:1px solid #ebe6e7;border-radius:14px;margin-top:18px;padding:14px}.share-popup .link-box label{color:#64748b;font-size:13px}.share-popup .link-row{display:flex;gap:8px;margin-top:6px}.share-popup .link-row input{background:transparent;border:none;color:#334155;flex:1;font-size:13px;letter-spacing:.8px;line-height:1.2;outline:none}.share-popup .link-row button{align-items:center;background:#fff;border:1px solid #ebe6e7;border-radius:10px;cursor:pointer;display:flex;height:35px;justify-content:center;padding:6px 8px;width:35px}.share-popup .success{color:#2563eb;display:block;font-size:12px;margin-top:6px;opacity:0;transition:opacity .3s}.share-popup .success.show{opacity:1}.share-popup .footer-text{color:#64748b;font-size:12px;margin-top:14px;text-align:center}.share-popup .share-card.twitter .icon{background:#000}.share-popup .share-card.facebook .icon{background:#1877f2}.share-popup .share-card.sms .icon{background:#059669}.share-popup .share-card.copied .icon{background:#2563eb}@keyframes entrance-fade-in{0%{opacity:0}to{opacity:1}}@keyframes entrance-scale-in{0%{opacity:0;transform:scale(.9) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes entrance-text-fade-in{0%{opacity:0}to{opacity:1}}@keyframes exit-blur-scale{0%{filter:blur(0);opacity:1;transform:scale(1)}to{filter:blur(10px);opacity:0;transform:scale(1.1)}}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.5}}.entrance-animation-overlay{align-items:center;animation:entrance-fade-in .8s ease-in-out forwards;background-color:#0f172a;color:#fff;display:flex;flex-direction:column;inset:0;justify-content:center;position:fixed;z-index:9999}.entrance-animation-overlay.exit{animation:exit-blur-scale .8s ease-in-out forwards}.entrance-content{align-items:center;animation:entrance-scale-in .5s ease-out .2s both;display:flex;flex-direction:column}.entrance-icon-box{align-items:center;background:#2563eb;border-radius:16px;box-shadow:0 10px 25px rgba(37,99,235,.2);display:flex;height:64px;justify-content:center;margin-bottom:24px;width:64px}.entrance-icon-box svg{color:#fff;height:32px;width:32px}.entrance-title{font-size:32px;font-weight:700;letter-spacing:-.5px;margin-bottom:12px}@media (min-width:768px){.entrance-title{font-size:48px}}.entrance-message{align-items:center;animation:entrance-text-fade-in .5s ease-out .5s both;color:#60a5fa;display:flex;font-size:18px;font-weight:500;gap:8px}.entrance-pulse-dot{animation:pulse-dot 1.5s ease-in-out infinite;background-color:#60a5fa;border-radius:50%;height:8px;width:8px}";
2160
2171
  styleInject(css_248z);
2161
2172
 
2162
2173
  /// <reference lib="dom" />
2174
+ const CONTACT_PROMPT_TITLE = "Protect your group spot";
2175
+ const CONTACT_PROMPT_SUBTITLE = "Add your email or phone so we can help you recover your group membership if you leave accidentally.";
2176
+ const CONTACT_PROMPT_PRIMARY_CTA = "Protect my spot";
2177
+ const CONTACT_PROMPT_REMIND_CTA = "Remind me later";
2178
+ const CONTACT_PROMPT_INVALID_INPUT = "Please enter a valid email or phone number.";
2179
+ const CONTACT_PROMPT_SAVE_ERROR = "Could not save contact right now. Please try again.";
2180
+ const CONTACT_PROTECTED_TOAST = "Spot protected. We can help recover your group membership.";
2181
+ const LEAVE_WARNING_TITLE = "Before you leave";
2182
+ const LEAVE_WARNING_SUBTITLE = "If you leave now without adding contact, you may lose this group spot and may not be able to rejoin the same group.";
2163
2183
  /**
2164
2184
  * LobbyModal - Renders and manages the group buying lobby modal
2165
2185
  */
2166
2186
  class LobbyModal {
2167
- constructor(data, callbacks, apiClient, socketManager = null, debug = false) {
2187
+ constructor(data, callbacks, apiClient, socketManager = null, analyticsClient = null, debug = false) {
2168
2188
  this.modalElement = null;
2169
2189
  this.timerInterval = null;
2170
2190
  this.activityInterval = null;
@@ -2173,6 +2193,17 @@ class LobbyModal {
2173
2193
  this.currentGroupId = null;
2174
2194
  this.shareOverlay = null;
2175
2195
  this.keyboardHandler = null;
2196
+ this.leaveFlowInProgress = false;
2197
+ this.leaveSignalSent = false;
2198
+ this.hasContactProtection = false;
2199
+ this.initialContactPromptTimer = null;
2200
+ this.contactPromptOverlay = null;
2201
+ this.leaveLoaderOverlay = null;
2202
+ this.beforeUnloadHandler = null;
2203
+ this.pageHideHandler = null;
2204
+ this.visibilityChangeHandler = null;
2205
+ this.CONTACT_PROTECTION_PREFIX = "cobuy_contact_protection";
2206
+ this.LAST_LEFT_GROUP_PREFIX = "cobuy_last_left_group";
2176
2207
  /**
2177
2208
  * Handle socket group update events
2178
2209
  */
@@ -2264,6 +2295,7 @@ class LobbyModal {
2264
2295
  this.logger = new Logger(debug);
2265
2296
  this.apiClient = apiClient;
2266
2297
  this.socketManager = socketManager;
2298
+ this.analyticsClient = analyticsClient;
2267
2299
  // Log the group data being passed into the modal
2268
2300
  this.logger.info("LobbyModal initialized with group data", {
2269
2301
  groupId: data.groupId,
@@ -2275,14 +2307,385 @@ class LobbyModal {
2275
2307
  timeLeft: data.timeLeft,
2276
2308
  offlineRedemption: data.offlineRedemption,
2277
2309
  });
2278
- this.data = Object.assign({ groupNumber: "1000", status: "active", progress: 80, currentMembers: 4, totalMembers: 5, timeLeft: 1390, discount: "20% OFF", isLocked: true, activities: this.getDefaultActivities() }, data);
2310
+ this.data = Object.assign({ groupNumber: "1000", status: "active", progress: 80, currentMembers: 4, totalMembers: 5, timeLeft: 1390, discount: "20% OFF", isLocked: true, activities: this.getDefaultActivities(), redemptionMethod: "online" }, data);
2279
2311
  // Normalize optional flags so undefined values don't override defaults
2280
2312
  this.data.isLocked = this.computeIsLocked(this.data);
2281
2313
  if (!this.data.activities || !Array.isArray(this.data.activities)) {
2282
2314
  this.data.activities = this.getDefaultActivities();
2283
2315
  }
2316
+ this.hasContactProtection = this.readContactProtectionState();
2284
2317
  this.callbacks = callbacks;
2285
2318
  }
2319
+ getSessionId() {
2320
+ var _a;
2321
+ return ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || "anonymous";
2322
+ }
2323
+ getContactProtectionStorageKey() {
2324
+ return `${this.CONTACT_PROTECTION_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
2325
+ }
2326
+ getLastLeftGroupStorageKey() {
2327
+ return `${this.LAST_LEFT_GROUP_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
2328
+ }
2329
+ readContactProtectionState() {
2330
+ if (typeof window === "undefined" || !window.localStorage) {
2331
+ return false;
2332
+ }
2333
+ try {
2334
+ return window.localStorage.getItem(this.getContactProtectionStorageKey()) === "1";
2335
+ }
2336
+ catch (_a) {
2337
+ return false;
2338
+ }
2339
+ }
2340
+ persistContactProtectionState(value) {
2341
+ this.hasContactProtection = value;
2342
+ if (typeof window === "undefined" || !window.localStorage) {
2343
+ return;
2344
+ }
2345
+ try {
2346
+ const key = this.getContactProtectionStorageKey();
2347
+ if (value) {
2348
+ window.localStorage.setItem(key, "1");
2349
+ }
2350
+ else {
2351
+ window.localStorage.removeItem(key);
2352
+ }
2353
+ }
2354
+ catch (_a) {
2355
+ // Ignore storage errors (private mode, quota exceeded, etc.)
2356
+ }
2357
+ }
2358
+ persistLastLeftGroup(groupId) {
2359
+ if (typeof window === "undefined" || !window.localStorage || !groupId) {
2360
+ return;
2361
+ }
2362
+ try {
2363
+ window.localStorage.setItem(this.getLastLeftGroupStorageKey(), JSON.stringify({
2364
+ groupId,
2365
+ productId: this.data.productId,
2366
+ leftAt: Date.now(),
2367
+ }));
2368
+ }
2369
+ catch (_a) {
2370
+ // Ignore storage errors
2371
+ }
2372
+ }
2373
+ clearInitialContactPromptTimer() {
2374
+ if (this.initialContactPromptTimer !== null) {
2375
+ window.clearTimeout(this.initialContactPromptTimer);
2376
+ this.initialContactPromptTimer = null;
2377
+ }
2378
+ }
2379
+ clearContactPromptOverlay() {
2380
+ if (this.contactPromptOverlay) {
2381
+ this.contactPromptOverlay.remove();
2382
+ this.contactPromptOverlay = null;
2383
+ }
2384
+ }
2385
+ showBlockingLoader(message) {
2386
+ if (!this.modalElement) {
2387
+ return;
2388
+ }
2389
+ this.hideBlockingLoader();
2390
+ const overlay = document.createElement("div");
2391
+ overlay.className = "lobby-blocking-loader-overlay";
2392
+ const card = document.createElement("div");
2393
+ card.className = "lobby-blocking-loader-card";
2394
+ const spinner = document.createElement("div");
2395
+ spinner.className = "lobby-blocking-loader-spinner";
2396
+ const text = document.createElement("p");
2397
+ text.className = "lobby-blocking-loader-text";
2398
+ text.textContent = message;
2399
+ card.appendChild(spinner);
2400
+ card.appendChild(text);
2401
+ overlay.appendChild(card);
2402
+ this.modalElement.appendChild(overlay);
2403
+ this.leaveLoaderOverlay = overlay;
2404
+ }
2405
+ hideBlockingLoader() {
2406
+ if (this.leaveLoaderOverlay) {
2407
+ this.leaveLoaderOverlay.remove();
2408
+ this.leaveLoaderOverlay = null;
2409
+ }
2410
+ }
2411
+ updateContactProtectionUI() {
2412
+ const banner = document.getElementById("lobbyContactProtectionBanner");
2413
+ const explicitLeaveContainer = document.getElementById("lobbyProtectedLeaveContainer");
2414
+ if (!banner)
2415
+ return;
2416
+ const canShowContactActions = this.data.status !== "complete" && Boolean(this.apiClient);
2417
+ if (!canShowContactActions) {
2418
+ banner.style.display = "none";
2419
+ if (explicitLeaveContainer) {
2420
+ explicitLeaveContainer.style.display = "none";
2421
+ }
2422
+ return;
2423
+ }
2424
+ if (this.hasContactProtection) {
2425
+ banner.style.display = "none";
2426
+ if (explicitLeaveContainer) {
2427
+ explicitLeaveContainer.style.display = "flex";
2428
+ }
2429
+ return;
2430
+ }
2431
+ banner.style.display = "flex";
2432
+ if (explicitLeaveContainer) {
2433
+ explicitLeaveContainer.style.display = "none";
2434
+ }
2435
+ }
2436
+ showToastMessage(message) {
2437
+ if (!this.modalElement) {
2438
+ return;
2439
+ }
2440
+ const existing = this.modalElement.querySelector(".lobby-contact-toast");
2441
+ if (existing) {
2442
+ existing.remove();
2443
+ }
2444
+ const toast = document.createElement("div");
2445
+ toast.className = "lobby-contact-toast";
2446
+ toast.textContent = message;
2447
+ this.modalElement.appendChild(toast);
2448
+ window.setTimeout(() => {
2449
+ toast.remove();
2450
+ }, 2400);
2451
+ }
2452
+ trackAnalyticsEvent(eventName, metadata) {
2453
+ if (!this.analyticsClient) {
2454
+ return;
2455
+ }
2456
+ this.analyticsClient
2457
+ .trackEvent(eventName, this.data.productId, Object.assign({ groupId: this.currentGroupId || this.data.groupId }, metadata))
2458
+ .catch((error) => {
2459
+ this.logger.warn(`[Analytics] Failed to track ${eventName}`, error);
2460
+ });
2461
+ }
2462
+ async saveContactValue(contactValue, promptSource) {
2463
+ const trimmed = contactValue.trim();
2464
+ if (!trimmed || !this.apiClient) {
2465
+ return false;
2466
+ }
2467
+ const contactType = this.inferContactType(trimmed);
2468
+ if (!contactType) {
2469
+ window.alert(CONTACT_PROMPT_INVALID_INPUT);
2470
+ return false;
2471
+ }
2472
+ const payload = contactType === "phone"
2473
+ ? { type: "phone", value: trimmed.replace(/\D/g, "") }
2474
+ : { type: "email", value: trimmed };
2475
+ const result = await this.apiClient.setContact(payload);
2476
+ if (!result.success) {
2477
+ this.logger.warn("Failed to save lobby contact", result.error);
2478
+ window.alert(CONTACT_PROMPT_SAVE_ERROR);
2479
+ return false;
2480
+ }
2481
+ this.persistContactProtectionState(true);
2482
+ this.trackAnalyticsEvent("CONTACT_INFO_SAVED", {
2483
+ promptSource,
2484
+ contactType,
2485
+ protectionState: "protected",
2486
+ });
2487
+ this.updateContactProtectionUI();
2488
+ this.showToastMessage(CONTACT_PROTECTED_TOAST);
2489
+ return true;
2490
+ }
2491
+ openContactProtectionPrompt(source) {
2492
+ if (typeof document === "undefined" || !this.modalElement) {
2493
+ return Promise.resolve("cancel");
2494
+ }
2495
+ this.clearContactPromptOverlay();
2496
+ this.trackAnalyticsEvent("CONTACT_PROMPT_SHOWN", {
2497
+ promptSource: source,
2498
+ protectionState: "unprotected",
2499
+ });
2500
+ return new Promise((resolve) => {
2501
+ var _a;
2502
+ const overlay = document.createElement("div");
2503
+ overlay.className = "lobby-contact-overlay";
2504
+ const card = document.createElement("div");
2505
+ card.className = "lobby-contact-card";
2506
+ const title = document.createElement("h3");
2507
+ title.className = "lobby-contact-title";
2508
+ title.textContent = CONTACT_PROMPT_TITLE;
2509
+ const subtitle = document.createElement("p");
2510
+ subtitle.className = "lobby-contact-subtitle";
2511
+ subtitle.textContent = CONTACT_PROMPT_SUBTITLE;
2512
+ const input = document.createElement("input");
2513
+ input.type = "text";
2514
+ input.className = "lobby-contact-input";
2515
+ input.placeholder = "Email or phone";
2516
+ input.autocomplete = "email";
2517
+ const actions = document.createElement("div");
2518
+ actions.className = "lobby-contact-actions";
2519
+ const primary = document.createElement("button");
2520
+ primary.type = "button";
2521
+ primary.className = "lobby-contact-primary";
2522
+ primary.textContent = CONTACT_PROMPT_PRIMARY_CTA;
2523
+ const secondary = document.createElement("button");
2524
+ secondary.type = "button";
2525
+ secondary.className = "lobby-contact-secondary";
2526
+ secondary.textContent = source === "leave-warning" ? "Cancel" : CONTACT_PROMPT_REMIND_CTA;
2527
+ const cleanupAndResolve = (result) => {
2528
+ this.clearContactPromptOverlay();
2529
+ resolve(result);
2530
+ };
2531
+ primary.addEventListener("click", async () => {
2532
+ const originalLabel = primary.textContent;
2533
+ primary.disabled = true;
2534
+ secondary.disabled = true;
2535
+ input.disabled = true;
2536
+ primary.classList.add("is-loading");
2537
+ primary.textContent = "Saving...";
2538
+ try {
2539
+ const saved = await this.saveContactValue(input.value, source);
2540
+ if (saved) {
2541
+ cleanupAndResolve("saved");
2542
+ }
2543
+ }
2544
+ finally {
2545
+ if (this.contactPromptOverlay) {
2546
+ primary.disabled = false;
2547
+ secondary.disabled = false;
2548
+ input.disabled = false;
2549
+ primary.classList.remove("is-loading");
2550
+ primary.textContent = originalLabel || CONTACT_PROMPT_PRIMARY_CTA;
2551
+ }
2552
+ }
2553
+ });
2554
+ secondary.addEventListener("click", () => {
2555
+ if (source !== "leave-warning") {
2556
+ this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
2557
+ promptSource: source,
2558
+ protectionState: "unprotected",
2559
+ });
2560
+ }
2561
+ cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
2562
+ });
2563
+ overlay.addEventListener("click", (event) => {
2564
+ if (event.target === overlay) {
2565
+ if (source !== "leave-warning") {
2566
+ this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
2567
+ promptSource: source,
2568
+ protectionState: "unprotected",
2569
+ });
2570
+ }
2571
+ cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
2572
+ }
2573
+ });
2574
+ actions.appendChild(primary);
2575
+ actions.appendChild(secondary);
2576
+ card.appendChild(title);
2577
+ card.appendChild(subtitle);
2578
+ card.appendChild(input);
2579
+ card.appendChild(actions);
2580
+ overlay.appendChild(card);
2581
+ (_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
2582
+ this.contactPromptOverlay = overlay;
2583
+ window.setTimeout(() => input.focus(), 0);
2584
+ });
2585
+ }
2586
+ openUnprotectedLeaveWarning() {
2587
+ if (typeof document === "undefined" || !this.modalElement) {
2588
+ return Promise.resolve("cancel");
2589
+ }
2590
+ this.clearContactPromptOverlay();
2591
+ return new Promise((resolve) => {
2592
+ var _a;
2593
+ const overlay = document.createElement("div");
2594
+ overlay.className = "lobby-contact-overlay";
2595
+ const card = document.createElement("div");
2596
+ card.className = "lobby-contact-card";
2597
+ const title = document.createElement("h3");
2598
+ title.className = "lobby-contact-title";
2599
+ title.textContent = LEAVE_WARNING_TITLE;
2600
+ const subtitle = document.createElement("p");
2601
+ subtitle.className = "lobby-contact-subtitle";
2602
+ subtitle.textContent = LEAVE_WARNING_SUBTITLE;
2603
+ const actions = document.createElement("div");
2604
+ actions.className = "lobby-contact-actions";
2605
+ const addContactBtn = document.createElement("button");
2606
+ addContactBtn.type = "button";
2607
+ addContactBtn.className = "lobby-contact-primary";
2608
+ addContactBtn.textContent = "Add contact first";
2609
+ const leaveBtn = document.createElement("button");
2610
+ leaveBtn.type = "button";
2611
+ leaveBtn.className = "lobby-contact-danger";
2612
+ leaveBtn.textContent = "Leave anyway";
2613
+ const cancelBtn = document.createElement("button");
2614
+ cancelBtn.type = "button";
2615
+ cancelBtn.className = "lobby-contact-secondary";
2616
+ cancelBtn.textContent = "Cancel";
2617
+ const cleanup = (result) => {
2618
+ this.clearContactPromptOverlay();
2619
+ resolve(result);
2620
+ };
2621
+ addContactBtn.addEventListener("click", () => cleanup("add_contact"));
2622
+ leaveBtn.addEventListener("click", () => cleanup("leave"));
2623
+ cancelBtn.addEventListener("click", () => cleanup("cancel"));
2624
+ overlay.addEventListener("click", (event) => {
2625
+ if (event.target === overlay) {
2626
+ cleanup("cancel");
2627
+ }
2628
+ });
2629
+ actions.appendChild(addContactBtn);
2630
+ actions.appendChild(leaveBtn);
2631
+ actions.appendChild(cancelBtn);
2632
+ card.appendChild(title);
2633
+ card.appendChild(subtitle);
2634
+ card.appendChild(actions);
2635
+ overlay.appendChild(card);
2636
+ (_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
2637
+ this.contactPromptOverlay = overlay;
2638
+ });
2639
+ }
2640
+ scheduleInitialContactPrompt() {
2641
+ if (!this.modalElement ||
2642
+ !this.shouldRunLeaveFlow() ||
2643
+ this.hasContactProtection ||
2644
+ !this.apiClient) {
2645
+ return;
2646
+ }
2647
+ this.clearInitialContactPromptTimer();
2648
+ this.initialContactPromptTimer = window.setTimeout(async () => {
2649
+ this.initialContactPromptTimer = null;
2650
+ if (!this.modalElement || this.hasContactProtection) {
2651
+ return;
2652
+ }
2653
+ await this.openContactProtectionPrompt("initial");
2654
+ this.updateContactProtectionUI();
2655
+ }, 2000);
2656
+ }
2657
+ async leaveGroupExplicitly() {
2658
+ var _a, _b;
2659
+ if (!this.apiClient || !this.currentGroupId || this.leaveFlowInProgress) {
2660
+ return;
2661
+ }
2662
+ this.leaveFlowInProgress = true;
2663
+ try {
2664
+ this.showBlockingLoader("Leaving group...");
2665
+ const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
2666
+ leave_source: "close_icon",
2667
+ leave_reason: "voluntary_with_contact",
2668
+ });
2669
+ if (!leaveResult.success) {
2670
+ this.logger.warn("Explicit leave group failed", leaveResult.error);
2671
+ this.hideBlockingLoader();
2672
+ window.alert("Could not leave the group right now. Please try again.");
2673
+ return;
2674
+ }
2675
+ this.leaveSignalSent = true;
2676
+ this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
2677
+ this.trackAnalyticsEvent("PROTECTED_USER_EXPLICIT_LEAVE", {
2678
+ leaveSource: "close_icon",
2679
+ leaveReason: "voluntary_with_contact",
2680
+ protectionState: "protected",
2681
+ });
2682
+ this.close({ skipLeaveFlow: true });
2683
+ }
2684
+ finally {
2685
+ this.hideBlockingLoader();
2686
+ this.leaveFlowInProgress = false;
2687
+ }
2688
+ }
2286
2689
  /**
2287
2690
  * Derive lock state from data so UI reflects completion
2288
2691
  */
@@ -2599,6 +3002,7 @@ class LobbyModal {
2599
3002
  * Create connected section (subtitle + link + share)
2600
3003
  */
2601
3004
  createConnectedSection() {
3005
+ var _a, _b;
2602
3006
  const connectedSection = document.createElement("div");
2603
3007
  connectedSection.className = "connected-section";
2604
3008
  connectedSection.id = "lobbyConnectedSection";
@@ -2609,18 +3013,59 @@ class LobbyModal {
2609
3013
  subtitle.id = "lobbySubtitleText";
2610
3014
  subtitle.textContent = titleContent.subtitle;
2611
3015
  connectedSection.appendChild(subtitle);
3016
+ const contactBanner = document.createElement("div");
3017
+ contactBanner.className = "lobby-contact-protection-banner";
3018
+ contactBanner.id = "lobbyContactProtectionBanner";
3019
+ const contactCopy = document.createElement("p");
3020
+ contactCopy.className = "lobby-contact-protection-copy";
3021
+ contactCopy.textContent =
3022
+ "Spot not protected. Add contact so we can help recover your group membership.";
3023
+ const contactAction = document.createElement("button");
3024
+ contactAction.type = "button";
3025
+ contactAction.className = "lobby-contact-protection-btn";
3026
+ contactAction.textContent = "Add contact";
3027
+ contactAction.addEventListener("click", async () => {
3028
+ await this.openContactProtectionPrompt("banner");
3029
+ this.updateContactProtectionUI();
3030
+ });
3031
+ contactBanner.appendChild(contactCopy);
3032
+ contactBanner.appendChild(contactAction);
3033
+ connectedSection.appendChild(contactBanner);
3034
+ const protectedLeaveContainer = document.createElement("div");
3035
+ protectedLeaveContainer.className = "lobby-protected-leave-container";
3036
+ protectedLeaveContainer.id = "lobbyProtectedLeaveContainer";
3037
+ const protectedLeaveText = document.createElement("p");
3038
+ protectedLeaveText.className = "lobby-protected-leave-copy";
3039
+ protectedLeaveText.textContent = "You are protected and still part of this group.";
3040
+ const protectedLeaveAction = document.createElement("button");
3041
+ protectedLeaveAction.type = "button";
3042
+ protectedLeaveAction.className = "lobby-protected-leave-btn";
3043
+ protectedLeaveAction.textContent = "Leave group";
3044
+ protectedLeaveAction.addEventListener("click", () => {
3045
+ void this.leaveGroupExplicitly();
3046
+ });
3047
+ protectedLeaveContainer.appendChild(protectedLeaveText);
3048
+ protectedLeaveContainer.appendChild(protectedLeaveAction);
3049
+ connectedSection.appendChild(protectedLeaveContainer);
2612
3050
  // Check if group is fulfilled and has offline redemption
2613
3051
  const isComplete = !this.computeIsLocked(this.data);
2614
3052
  const hasOfflineRedemption = isComplete &&
2615
3053
  this.data.offlineRedemption &&
2616
- isValidOfflineRedemption(this.data.offlineRedemption);
3054
+ isValidOfflineRedemption(this.data.offlineRedemption) &&
3055
+ ((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
2617
3056
  if (hasOfflineRedemption) {
2618
- // Show offline redemption view with integrated actions
3057
+ // Complete + offline/both: show offline redemption section
2619
3058
  const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
2620
3059
  connectedSection.appendChild(offlineSection);
2621
3060
  }
3061
+ else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
3062
+ // Complete + online/both (no offline redemption data): show only checkout CTA
3063
+ const checkoutBtn = this.createOnlineCheckoutButton();
3064
+ connectedSection.appendChild(checkoutBtn);
3065
+ this.injectOfflineRedemptionStyles();
3066
+ }
2622
3067
  else {
2623
- // Show link and share container
3068
+ // Group not yet complete: show link and share
2624
3069
  const linkShareContainer = document.createElement("div");
2625
3070
  linkShareContainer.className = "link-share-container";
2626
3071
  const linkWp = this.createLinkSection();
@@ -2664,6 +3109,23 @@ class LobbyModal {
2664
3109
  linkShareWrapper.appendChild(shareBtnInner);
2665
3110
  return linkShareWrapper;
2666
3111
  }
3112
+ /**
3113
+ * Create a standalone "Checkout Online" button for the link/share section
3114
+ */
3115
+ createOnlineCheckoutButton() {
3116
+ const btn = document.createElement("button");
3117
+ btn.className = "offline-online-checkout-btn lobby-online-checkout-btn";
3118
+ btn.textContent = "Checkout Online";
3119
+ btn.style.marginTop = "10px";
3120
+ btn.style.width = "100%";
3121
+ btn.addEventListener("click", () => {
3122
+ this.close();
3123
+ window.setTimeout(() => {
3124
+ this.triggerContinueToCheckout();
3125
+ }, 0);
3126
+ });
3127
+ return btn;
3128
+ }
2667
3129
  /**
2668
3130
  * Create offline redemption section
2669
3131
  */
@@ -2728,14 +3190,16 @@ class LobbyModal {
2728
3190
  onlineCheckoutBtn.className = "offline-online-checkout-btn";
2729
3191
  onlineCheckoutBtn.textContent = "Checkout Online";
2730
3192
  onlineCheckoutBtn.addEventListener("click", () => {
2731
- this.close();
3193
+ this.close({ skipLeaveFlow: true });
2732
3194
  // Ensure modal closes before triggering checkout intent
2733
3195
  window.setTimeout(() => {
2734
3196
  this.triggerContinueToCheckout();
2735
3197
  }, 0);
2736
3198
  });
2737
3199
  actionsRow.appendChild(downloadQRBtn);
2738
- actionsRow.appendChild(onlineCheckoutBtn);
3200
+ if (this.data.redemptionMethod !== "offline") {
3201
+ actionsRow.appendChild(onlineCheckoutBtn);
3202
+ }
2739
3203
  section.appendChild(topRow);
2740
3204
  section.appendChild(expiryInfo);
2741
3205
  section.appendChild(actionsRow);
@@ -3641,6 +4105,10 @@ class LobbyModal {
3641
4105
  this.logger.info(`Lobby modal opened for product: ${this.data.productId}`);
3642
4106
  // Set up keyboard accessibility (focus trap, ESC to close)
3643
4107
  this.setupKeyboardAccessibility();
4108
+ // Register lifecycle-based dropoff handling for browser/tab close.
4109
+ this.registerLifecycleLeaveHandlers();
4110
+ this.updateContactProtectionUI();
4111
+ this.scheduleInitialContactPrompt();
3644
4112
  });
3645
4113
  }
3646
4114
  /**
@@ -3706,19 +4174,166 @@ class LobbyModal {
3706
4174
  this.keyboardHandler = null;
3707
4175
  }
3708
4176
  }
4177
+ registerLifecycleLeaveHandlers() {
4178
+ if (typeof window === "undefined" || typeof document === "undefined") {
4179
+ return;
4180
+ }
4181
+ if (!this.shouldRunLeaveFlow()) {
4182
+ return;
4183
+ }
4184
+ if (!this.beforeUnloadHandler) {
4185
+ this.beforeUnloadHandler = () => {
4186
+ this.triggerBestEffortLeave("browser_close");
4187
+ };
4188
+ window.addEventListener("beforeunload", this.beforeUnloadHandler);
4189
+ }
4190
+ if (!this.pageHideHandler) {
4191
+ this.pageHideHandler = () => {
4192
+ this.triggerBestEffortLeave("browser_close");
4193
+ };
4194
+ window.addEventListener("pagehide", this.pageHideHandler);
4195
+ }
4196
+ if (!this.visibilityChangeHandler) {
4197
+ this.visibilityChangeHandler = () => {
4198
+ if (document.visibilityState === "hidden") {
4199
+ this.triggerBestEffortLeave("browser_close");
4200
+ }
4201
+ };
4202
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
4203
+ }
4204
+ }
4205
+ unregisterLifecycleLeaveHandlers() {
4206
+ if (typeof window === "undefined" || typeof document === "undefined") {
4207
+ return;
4208
+ }
4209
+ if (this.beforeUnloadHandler) {
4210
+ window.removeEventListener("beforeunload", this.beforeUnloadHandler);
4211
+ this.beforeUnloadHandler = null;
4212
+ }
4213
+ if (this.pageHideHandler) {
4214
+ window.removeEventListener("pagehide", this.pageHideHandler);
4215
+ this.pageHideHandler = null;
4216
+ }
4217
+ if (this.visibilityChangeHandler) {
4218
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
4219
+ this.visibilityChangeHandler = null;
4220
+ }
4221
+ }
4222
+ triggerBestEffortLeave(source) {
4223
+ if (this.leaveSignalSent || this.leaveFlowInProgress) {
4224
+ return;
4225
+ }
4226
+ if (this.hasContactProtection) {
4227
+ return;
4228
+ }
4229
+ if (!this.apiClient || !this.currentGroupId || !this.shouldRunLeaveFlow()) {
4230
+ return;
4231
+ }
4232
+ this.leaveSignalSent = true;
4233
+ this.apiClient.leaveGroupBestEffort(this.currentGroupId, {
4234
+ leave_source: source,
4235
+ leave_reason: "voluntary_no_contact",
4236
+ });
4237
+ }
4238
+ shouldRunLeaveFlow() {
4239
+ if (!this.apiClient || !this.currentGroupId) {
4240
+ return false;
4241
+ }
4242
+ // Dropoff handling applies only while group is still active/forming.
4243
+ return this.data.status !== "complete";
4244
+ }
4245
+ inferContactType(value) {
4246
+ const trimmed = value.trim();
4247
+ if (!trimmed)
4248
+ return null;
4249
+ if (trimmed.includes("@")) {
4250
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4251
+ return emailRegex.test(trimmed) ? "email" : null;
4252
+ }
4253
+ const digitsOnly = trimmed.replace(/\D/g, "");
4254
+ return digitsOnly.length >= 10 && digitsOnly.length <= 15 ? "phone" : null;
4255
+ }
4256
+ async runLeaveFlowAndClose() {
4257
+ var _a, _b;
4258
+ if (this.leaveFlowInProgress)
4259
+ return;
4260
+ this.leaveFlowInProgress = true;
4261
+ try {
4262
+ if (this.hasContactProtection) {
4263
+ this.trackAnalyticsEvent("LOBBY_CLOSED_PROTECTED", {
4264
+ leaveSource: "close_icon",
4265
+ leaveReason: "stay_in_group",
4266
+ protectionState: "protected",
4267
+ });
4268
+ this.close({ skipLeaveFlow: true });
4269
+ return;
4270
+ }
4271
+ let leaveReason = "voluntary_no_contact";
4272
+ const leaveDecision = await this.openUnprotectedLeaveWarning();
4273
+ if (leaveDecision === "cancel") {
4274
+ return;
4275
+ }
4276
+ if (leaveDecision === "add_contact") {
4277
+ const promptResult = await this.openContactProtectionPrompt("leave-warning");
4278
+ if (promptResult === "saved") {
4279
+ this.updateContactProtectionUI();
4280
+ return;
4281
+ }
4282
+ else {
4283
+ return;
4284
+ }
4285
+ }
4286
+ if (this.apiClient && this.currentGroupId) {
4287
+ this.showBlockingLoader("Leaving group...");
4288
+ const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
4289
+ leave_source: "close_icon",
4290
+ leave_reason: leaveReason,
4291
+ });
4292
+ if (!leaveResult.success) {
4293
+ this.logger.warn("Leave group failed", leaveResult.error);
4294
+ this.hideBlockingLoader();
4295
+ window.alert("Could not leave the group right now. Please try again.");
4296
+ return;
4297
+ }
4298
+ this.leaveSignalSent = true;
4299
+ this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
4300
+ this.trackAnalyticsEvent("LOBBY_LEFT_UNPROTECTED", {
4301
+ leaveSource: "close_icon",
4302
+ leaveReason,
4303
+ protectionState: "unprotected",
4304
+ });
4305
+ }
4306
+ this.close({ skipLeaveFlow: true });
4307
+ }
4308
+ finally {
4309
+ this.hideBlockingLoader();
4310
+ this.leaveFlowInProgress = false;
4311
+ }
4312
+ }
3709
4313
  /**
3710
4314
  * Close the modal
3711
4315
  */
3712
- close() {
4316
+ close(options = {}) {
3713
4317
  if (!this.modalElement) {
3714
4318
  this.logger.warn("Modal not open");
3715
4319
  return;
3716
4320
  }
4321
+ if (!options.skipLeaveFlow && this.shouldRunLeaveFlow()) {
4322
+ void this.runLeaveFlowAndClose();
4323
+ return;
4324
+ }
3717
4325
  // Stop timers and animations
3718
4326
  this.stopTimer();
3719
4327
  this.stopActivityAnimation();
3720
4328
  // Remove keyboard event listeners
3721
4329
  this.removeKeyboardAccessibility();
4330
+ // Remove lifecycle handlers for dropoff detection
4331
+ this.unregisterLifecycleLeaveHandlers();
4332
+ this.clearInitialContactPromptTimer();
4333
+ this.clearContactPromptOverlay();
4334
+ this.hideBlockingLoader();
4335
+ // Remove socket listeners to avoid duplicate handlers on reopen
4336
+ this.unsubscribeFromSocketEvents();
3722
4337
  // Remove modal from DOM
3723
4338
  document.body.removeChild(this.modalElement);
3724
4339
  this.modalElement = null;
@@ -3831,29 +4446,47 @@ class LobbyModal {
3831
4446
  * Update offline redemption visibility when group is fulfilled
3832
4447
  */
3833
4448
  updateOfflineRedemptionVisibility() {
4449
+ var _a, _b;
3834
4450
  const connectedSection = document.getElementById("lobbyConnectedSection");
3835
4451
  if (!connectedSection)
3836
4452
  return;
3837
4453
  const isComplete = !this.computeIsLocked(this.data);
3838
- const hasOfflineRedemption = this.data.offlineRedemption && isValidOfflineRedemption(this.data.offlineRedemption);
4454
+ const hasOfflineRedemption = this.data.offlineRedemption &&
4455
+ isValidOfflineRedemption(this.data.offlineRedemption) &&
4456
+ ((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
3839
4457
  // Get existing elements
3840
4458
  const existingOffline = connectedSection.querySelector(".offline-redemption-section");
3841
4459
  const existingLink = connectedSection.querySelector(".link-share-container");
4460
+ const existingCheckoutBtn = connectedSection.querySelector(".lobby-online-checkout-btn");
3842
4461
  if (isComplete && hasOfflineRedemption) {
3843
- // Show offline redemption, hide link/share
3844
- if (existingLink) {
4462
+ // Complete + offline/both: show offline section, remove link/share and standalone checkout btn
4463
+ if (existingLink)
3845
4464
  existingLink.remove();
3846
- }
4465
+ if (existingCheckoutBtn)
4466
+ existingCheckoutBtn.remove();
3847
4467
  if (!existingOffline) {
3848
4468
  const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
3849
4469
  connectedSection.appendChild(offlineSection);
3850
4470
  }
3851
4471
  }
3852
- else {
3853
- // Show link/share, hide offline redemption
3854
- if (existingOffline) {
4472
+ else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
4473
+ // Complete + online/both (no offline data): show only checkout CTA, remove link/share
4474
+ if (existingLink)
4475
+ existingLink.remove();
4476
+ if (existingOffline)
3855
4477
  existingOffline.remove();
4478
+ if (!existingCheckoutBtn) {
4479
+ const checkoutBtn = this.createOnlineCheckoutButton();
4480
+ connectedSection.appendChild(checkoutBtn);
4481
+ this.injectOfflineRedemptionStyles();
3856
4482
  }
4483
+ }
4484
+ else {
4485
+ // Group not yet complete: show link/share, remove offline and checkout btn
4486
+ if (existingOffline)
4487
+ existingOffline.remove();
4488
+ if (existingCheckoutBtn)
4489
+ existingCheckoutBtn.remove();
3857
4490
  if (!existingLink) {
3858
4491
  const linkShareContainer = document.createElement("div");
3859
4492
  linkShareContainer.className = "link-share-container";
@@ -3887,9 +4520,20 @@ class LobbyModal {
3887
4520
  }
3888
4521
  window.addEventListener("group:fulfilled", this.handleSocketGroupUpdate);
3889
4522
  window.addEventListener("group:member:joined", this.handleSocketGroupUpdate);
4523
+ window.addEventListener("group:member:left", this.handleSocketGroupUpdate);
3890
4524
  window.addEventListener("group:created", this.handleSocketGroupUpdate);
3891
4525
  this.socketListenerRegistered = true;
3892
4526
  }
4527
+ unsubscribeFromSocketEvents() {
4528
+ if (typeof window === "undefined" || !this.socketListenerRegistered) {
4529
+ return;
4530
+ }
4531
+ window.removeEventListener("group:fulfilled", this.handleSocketGroupUpdate);
4532
+ window.removeEventListener("group:member:joined", this.handleSocketGroupUpdate);
4533
+ window.removeEventListener("group:member:left", this.handleSocketGroupUpdate);
4534
+ window.removeEventListener("group:created", this.handleSocketGroupUpdate);
4535
+ this.socketListenerRegistered = false;
4536
+ }
3893
4537
  /**
3894
4538
  * Create activity item from socket event data
3895
4539
  */
@@ -3920,6 +4564,10 @@ class LobbyModal {
3920
4564
  emoji = "🛒";
3921
4565
  action = "started a new group";
3922
4566
  break;
4567
+ case "group:member:left":
4568
+ emoji = "🚪";
4569
+ action = "left the group";
4570
+ break;
3923
4571
  default:
3924
4572
  emoji = "⚡";
3925
4573
  action = "activity in group";
@@ -4392,6 +5040,9 @@ var WidgetState;
4392
5040
  WidgetState["LOADED"] = "loaded";
4393
5041
  WidgetState["ERROR"] = "error";
4394
5042
  })(WidgetState || (WidgetState = {}));
5043
+ const RECOVERY_TOAST_REJOINED = "Rejoined your previous group.";
5044
+ const RECOVERY_TOAST_NEXT_AVAILABLE = "Your previous group was full, so we joined you to the next available group.";
5045
+ const RECOVERY_TOAST_CREATED_NEW = "Your previous group was no longer available, so we created a fresh group for you.";
4395
5046
  /**
4396
5047
  * WidgetRoot handles rendering of the CoBuy widget into a DOM container
4397
5048
  */
@@ -4423,6 +5074,7 @@ class WidgetRoot {
4423
5074
  this.groupExpiryRefreshTriggered = false; // Track if expiry refresh already triggered for current group
4424
5075
  this.offlineRedemption = null;
4425
5076
  this.offlineRedemptionModal = null;
5077
+ this.campaignRedemptionMethod = "online";
4426
5078
  this.isRendering = false;
4427
5079
  this.renderPromise = null;
4428
5080
  this.liveRegionAnnouncer = null;
@@ -4432,6 +5084,7 @@ class WidgetRoot {
4432
5084
  this.renderDebounceTimer = null;
4433
5085
  this.pendingRenderOptions = null;
4434
5086
  this.RENDER_DEBOUNCE_MS = 300; // 300ms debounce for rapid re-renders
5087
+ this.LAST_LEFT_GROUP_PREFIX = "cobuy_last_left_group";
4435
5088
  /** Handle backend fulfillment notifications */
4436
5089
  this.handleGroupFulfilledEvent = (event) => {
4437
5090
  const detail = event.detail;
@@ -4475,6 +5128,80 @@ class WidgetRoot {
4475
5128
  this.handleCTAClick(productId);
4476
5129
  }, 300); // 300ms debounce to prevent double-clicks
4477
5130
  }
5131
+ getRecoveryStorageKey(productId) {
5132
+ var _a;
5133
+ const sessionId = (_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId();
5134
+ if (!sessionId || !productId) {
5135
+ return null;
5136
+ }
5137
+ return `${this.LAST_LEFT_GROUP_PREFIX}:${sessionId}:${productId}`;
5138
+ }
5139
+ getStoredRecoveryGroupId(productId) {
5140
+ if (typeof window === "undefined" || !window.localStorage) {
5141
+ return null;
5142
+ }
5143
+ const key = this.getRecoveryStorageKey(productId);
5144
+ if (!key) {
5145
+ return null;
5146
+ }
5147
+ try {
5148
+ const raw = window.localStorage.getItem(key);
5149
+ if (!raw) {
5150
+ return null;
5151
+ }
5152
+ const parsed = JSON.parse(raw);
5153
+ if (!(parsed === null || parsed === void 0 ? void 0 : parsed.groupId) || parsed.productId !== productId) {
5154
+ return null;
5155
+ }
5156
+ return parsed.groupId;
5157
+ }
5158
+ catch (_a) {
5159
+ return null;
5160
+ }
5161
+ }
5162
+ clearStoredRecoveryGroupId(productId) {
5163
+ if (typeof window === "undefined" || !window.localStorage) {
5164
+ return;
5165
+ }
5166
+ const key = this.getRecoveryStorageKey(productId);
5167
+ if (!key) {
5168
+ return;
5169
+ }
5170
+ try {
5171
+ window.localStorage.removeItem(key);
5172
+ }
5173
+ catch (_a) {
5174
+ // Ignore storage issues
5175
+ }
5176
+ }
5177
+ showRecoveryToast(message) {
5178
+ if (typeof document === "undefined") {
5179
+ return;
5180
+ }
5181
+ const existing = document.getElementById("cobuy-recovery-toast");
5182
+ if (existing) {
5183
+ existing.remove();
5184
+ }
5185
+ const toast = document.createElement("div");
5186
+ toast.id = "cobuy-recovery-toast";
5187
+ toast.style.position = "fixed";
5188
+ toast.style.left = "50%";
5189
+ toast.style.bottom = "24px";
5190
+ toast.style.transform = "translateX(-50%)";
5191
+ toast.style.background = "#0f172a";
5192
+ toast.style.color = "#fff";
5193
+ toast.style.padding = "10px 14px";
5194
+ toast.style.borderRadius = "10px";
5195
+ toast.style.fontSize = "13px";
5196
+ toast.style.fontWeight = "600";
5197
+ toast.style.zIndex = "10040";
5198
+ toast.style.boxShadow = "0 8px 20px rgba(0,0,0,0.24)";
5199
+ toast.textContent = message;
5200
+ document.body.appendChild(toast);
5201
+ window.setTimeout(() => {
5202
+ toast.remove();
5203
+ }, 2800);
5204
+ }
4478
5205
  /** Subscribe once to backend socket events routed through the host page */
4479
5206
  subscribeToSocketEvents() {
4480
5207
  if (typeof window === "undefined" || this.socketListenerRegistered) {
@@ -4482,6 +5209,7 @@ class WidgetRoot {
4482
5209
  }
4483
5210
  window.addEventListener("group:fulfilled", this.handleGroupFulfilledEvent);
4484
5211
  window.addEventListener("group:member:joined", this.handleGroupUpdatedEvent);
5212
+ window.addEventListener("group:member:left", this.handleGroupUpdatedEvent);
4485
5213
  window.addEventListener("group:created", this.handleGroupUpdatedEvent);
4486
5214
  this.socketListenerRegistered = true;
4487
5215
  }
@@ -4518,6 +5246,9 @@ class WidgetRoot {
4518
5246
  isValidOfflineRedemption(groupData.offline_redemption)) {
4519
5247
  this.offlineRedemption = groupData.offline_redemption;
4520
5248
  }
5249
+ if (groupData.campaign_redemption_method) {
5250
+ this.campaignRedemptionMethod = groupData.campaign_redemption_method;
5251
+ }
4521
5252
  }
4522
5253
  }
4523
5254
  else {
@@ -4613,7 +5344,7 @@ class WidgetRoot {
4613
5344
  }
4614
5345
  /** Persist fulfilled state, emit callback, and refresh UI */
4615
5346
  processGroupFulfilled(eventData) {
4616
- var _a, _b, _c, _d;
5347
+ var _a, _b, _c, _d, _e;
4617
5348
  this.groupFulfilled = true;
4618
5349
  // Extract reward from new event structure or fallback to legacy
4619
5350
  const reward = ((_a = eventData.frozen_reward) === null || _a === void 0 ? void 0 : _a.reward) || eventData.reward || null;
@@ -4628,7 +5359,10 @@ class WidgetRoot {
4628
5359
  code: this.offlineRedemption.redemption_code,
4629
5360
  });
4630
5361
  }
4631
- (_d = (_c = this.events) === null || _c === void 0 ? void 0 : _c.onGroupFulfilled) === null || _d === void 0 ? void 0 : _d.call(_c, eventData);
5362
+ if ((_c = eventData.group) === null || _c === void 0 ? void 0 : _c.campaign_redemption_method) {
5363
+ this.campaignRedemptionMethod = eventData.group.campaign_redemption_method;
5364
+ }
5365
+ (_e = (_d = this.events) === null || _d === void 0 ? void 0 : _d.onGroupFulfilled) === null || _e === void 0 ? void 0 : _e.call(_d, eventData);
4632
5366
  this.renderFulfilledState();
4633
5367
  }
4634
5368
  /** Re-render widget and external containers to reflect fulfillment */
@@ -4768,8 +5502,10 @@ class WidgetRoot {
4768
5502
  footer.style.justifyContent = "flex-end";
4769
5503
  footer.style.alignItems = "center";
4770
5504
  footer.style.gap = "12px";
4771
- // Add "Redeem In-store" CTA if offline redemption is available
4772
- if (this.offlineRedemption && isValidOfflineRedemption(this.offlineRedemption)) {
5505
+ // Add "Redeem In-store" CTA if offline redemption is available and campaign allows it
5506
+ if (this.offlineRedemption &&
5507
+ isValidOfflineRedemption(this.offlineRedemption) &&
5508
+ this.campaignRedemptionMethod !== "online") {
4773
5509
  const redeemLink = document.createElement("button");
4774
5510
  redeemLink.className = "cobuy-redeem-instore-link";
4775
5511
  redeemLink.style.background = "none";
@@ -4867,7 +5603,7 @@ class WidgetRoot {
4867
5603
  }
4868
5604
  // Set callback to open lobby when a group is successfully joined
4869
5605
  this.groupListModal.setOnGroupJoined((joinData) => {
4870
- var _a;
5606
+ var _a, _b;
4871
5607
  // Update current group ID and session ID so the modal knows which group the user has joined
4872
5608
  this.currentGroupId = joinData.group.id;
4873
5609
  this.currentSessionId = ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || null;
@@ -4881,8 +5617,9 @@ class WidgetRoot {
4881
5617
  currentMembers: joinData.group.participants_count,
4882
5618
  totalMembers: joinData.group.max_participants,
4883
5619
  timeLeft: joinData.group.timeLeftSeconds,
5620
+ redemptionMethod: (_b = joinData.group.campaign_redemption_method) !== null && _b !== void 0 ? _b : this.campaignRedemptionMethod,
4884
5621
  };
4885
- const lobbyModal = new LobbyModal(lobbyData, {}, null, null, this.config.debug);
5622
+ const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
4886
5623
  lobbyModal.open(joinData.group.id);
4887
5624
  });
4888
5625
  // Set callback for viewing progress on already joined group
@@ -4898,8 +5635,9 @@ class WidgetRoot {
4898
5635
  progress: Math.round((groupData.joined / groupData.total) * 100),
4899
5636
  currentMembers: groupData.joined,
4900
5637
  totalMembers: groupData.total,
5638
+ redemptionMethod: this.campaignRedemptionMethod,
4901
5639
  };
4902
- const lobbyModal = new LobbyModal(lobbyData, {}, null, null, this.config.debug);
5640
+ const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
4903
5641
  lobbyModal.open(groupId);
4904
5642
  });
4905
5643
  }
@@ -5083,6 +5821,9 @@ class WidgetRoot {
5083
5821
  isValidOfflineRedemption(groupData.offline_redemption)) {
5084
5822
  this.offlineRedemption = groupData.offline_redemption;
5085
5823
  }
5824
+ if (groupData === null || groupData === void 0 ? void 0 : groupData.campaign_redemption_method) {
5825
+ this.campaignRedemptionMethod = groupData.campaign_redemption_method;
5826
+ }
5086
5827
  // Check if group is already fulfilled (full) on initial load
5087
5828
  if (groupData) {
5088
5829
  const participants = Number(groupData.participants_count || 0);
@@ -5804,10 +6545,99 @@ class WidgetRoot {
5804
6545
  to { transform: rotate(360deg); }
5805
6546
  }
5806
6547
 
6548
+ .cobuy-prejoin-contact-overlay {
6549
+ position: fixed;
6550
+ inset: 0;
6551
+ z-index: 10045;
6552
+ background: rgba(2, 6, 23, 0.5);
6553
+ display: flex;
6554
+ align-items: center;
6555
+ justify-content: center;
6556
+ padding: 16px;
6557
+ }
6558
+
6559
+ .cobuy-prejoin-contact-card {
6560
+ width: min(440px, 100%);
6561
+ background: #fff;
6562
+ border-radius: 14px;
6563
+ padding: 18px;
6564
+ box-shadow: 0 14px 36px rgba(15, 23, 42, 0.22);
6565
+ display: flex;
6566
+ flex-direction: column;
6567
+ gap: 10px;
6568
+ }
6569
+
6570
+ .cobuy-prejoin-contact-title {
6571
+ font-size: 20px;
6572
+ font-weight: 800;
6573
+ color: #0f172a;
6574
+ margin: 0;
6575
+ }
6576
+
6577
+ .cobuy-prejoin-contact-subtitle {
6578
+ font-size: 14px;
6579
+ line-height: 1.5;
6580
+ color: #334155;
6581
+ margin: 0;
6582
+ }
6583
+
6584
+ .cobuy-prejoin-contact-input {
6585
+ width: 100%;
6586
+ border: 1px solid #cbd5e1;
6587
+ border-radius: 10px;
6588
+ padding: 11px 12px;
6589
+ font-size: 14px;
6590
+ outline: none;
6591
+ }
6592
+
6593
+ .cobuy-prejoin-contact-input:focus {
6594
+ border-color: #1d4ed8;
6595
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
6596
+ }
6597
+
6598
+ .cobuy-prejoin-contact-actions {
6599
+ display: flex;
6600
+ gap: 8px;
6601
+ flex-wrap: wrap;
6602
+ }
6603
+
6604
+ .cobuy-prejoin-contact-primary,
6605
+ .cobuy-prejoin-contact-secondary {
6606
+ border: none;
6607
+ border-radius: 10px;
6608
+ padding: 10px 12px;
6609
+ font-weight: 700;
6610
+ cursor: pointer;
6611
+ font-size: 13px;
6612
+ }
6613
+
6614
+ .cobuy-prejoin-contact-primary {
6615
+ background: #1d4ed8;
6616
+ color: #fff;
6617
+ }
6618
+
6619
+ .cobuy-prejoin-contact-primary:hover {
6620
+ background: #1e40af;
6621
+ }
6622
+
6623
+ .cobuy-prejoin-contact-secondary {
6624
+ background: #e2e8f0;
6625
+ color: #1e293b;
6626
+ }
6627
+
6628
+ .cobuy-prejoin-contact-secondary:hover {
6629
+ background: #cbd5e1;
6630
+ }
6631
+
5807
6632
  @media (max-width: 640px) {
5808
6633
  .cobuy-widget {
5809
6634
  grid-template-columns: 1fr;
5810
6635
  }
6636
+
6637
+ .cobuy-prejoin-contact-primary,
6638
+ .cobuy-prejoin-contact-secondary {
6639
+ width: 100%;
6640
+ }
5811
6641
  }
5812
6642
  `;
5813
6643
  document.head.appendChild(style);
@@ -5816,7 +6646,7 @@ class WidgetRoot {
5816
6646
  * Handle CTA button click with analytics and modal opening
5817
6647
  */
5818
6648
  async handleCTAClick(productId) {
5819
- var _a, _b, _c, _d, _e, _f, _g;
6649
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
5820
6650
  this.logger.info(`CTA clicked for product: ${productId}`);
5821
6651
  // Track analytics event asynchronously (fire-and-forget)
5822
6652
  if (this.analyticsClient) {
@@ -5831,28 +6661,110 @@ class WidgetRoot {
5831
6661
  // Join group before opening modal
5832
6662
  let groupJoinData = null;
5833
6663
  let inviteData = null;
6664
+ let recoveryGroupId = null;
5834
6665
  if (this.apiClient && this.currentGroupId) {
5835
6666
  try {
5836
- this.logger.info(`Joining group: ${this.currentGroupId}`);
5837
- if (this.analyticsClient) {
6667
+ recoveryGroupId = this.getStoredRecoveryGroupId(productId);
6668
+ if (this.analyticsClient && recoveryGroupId) {
5838
6669
  this.analyticsClient
5839
- .trackJoinAttempt(productId, this.currentGroupId)
6670
+ .trackEvent("RECOVERY_ATTEMPT", productId, {
6671
+ previousGroupId: recoveryGroupId,
6672
+ protectionState: "protected",
6673
+ })
5840
6674
  .catch((e) => this.logger.warn("Analytics tracking failed", e));
5841
6675
  }
5842
- const joinResponse = await this.apiClient.joinGroup(this.currentGroupId);
5843
- if (!joinResponse.success) {
5844
- this.logger.error("Failed to join group, modal will not open", joinResponse.error);
6676
+ const recoverResponse = await this.apiClient.recoverOrJoinGroup(productId);
6677
+ if (recoverResponse.success && recoverResponse.data) {
6678
+ groupJoinData = recoverResponse.data;
6679
+ this.currentGroupId = groupJoinData.group.id;
6680
+ this.clearStoredRecoveryGroupId(productId);
6681
+ if (this.analyticsClient && groupJoinData.recovery.recovered) {
6682
+ this.analyticsClient
6683
+ .trackEvent("RECOVERY_SUCCESS", productId, {
6684
+ previousGroupId: groupJoinData.recovery.previous_group_id || recoveryGroupId,
6685
+ joinedGroupId: groupJoinData.recovery.joined_group_id,
6686
+ recoveryOutcome: groupJoinData.recovery.outcome,
6687
+ protectionState: groupJoinData.recovery.matched_by === "none" ? "unprotected" : "protected",
6688
+ })
6689
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
6690
+ }
6691
+ else if (this.analyticsClient && recoveryGroupId) {
6692
+ this.analyticsClient
6693
+ .trackEvent("RECOVERY_FAILED", productId, {
6694
+ previousGroupId: recoveryGroupId,
6695
+ errorCode: "NOT_RECOVERED",
6696
+ errorMessage: "Recovery did not restore the previous membership.",
6697
+ protectionState: "protected",
6698
+ })
6699
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
6700
+ }
6701
+ switch (groupJoinData.recovery.outcome) {
6702
+ case "rejoined_previous_group":
6703
+ this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
6704
+ break;
6705
+ case "joined_next_available_group":
6706
+ if (groupJoinData.recovery.recovered) {
6707
+ this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
6708
+ }
6709
+ break;
6710
+ case "created_new_group":
6711
+ if (groupJoinData.recovery.recovered) {
6712
+ this.showRecoveryToast(RECOVERY_TOAST_CREATED_NEW);
6713
+ }
6714
+ break;
6715
+ }
6716
+ }
6717
+ else {
6718
+ if (this.analyticsClient && recoveryGroupId) {
6719
+ const errCode = (_c = recoverResponse.error) === null || _c === void 0 ? void 0 : _c.code;
6720
+ const errMsg = (_d = recoverResponse.error) === null || _d === void 0 ? void 0 : _d.message;
6721
+ this.analyticsClient
6722
+ .trackEvent("RECOVERY_FAILED", productId, {
6723
+ previousGroupId: recoveryGroupId,
6724
+ errorCode: errCode || "RECOVERY_UNAVAILABLE",
6725
+ errorMessage: errMsg || "Recovery response was not successful.",
6726
+ protectionState: "protected",
6727
+ })
6728
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
6729
+ }
6730
+ const joinTargetGroupId = recoveryGroupId || this.currentGroupId;
6731
+ this.logger.info(`Joining group: ${joinTargetGroupId}`);
5845
6732
  if (this.analyticsClient) {
5846
- const errCode = (_c = joinResponse.error) === null || _c === void 0 ? void 0 : _c.code;
5847
- const errMsg = (_d = joinResponse.error) === null || _d === void 0 ? void 0 : _d.message;
5848
6733
  this.analyticsClient
5849
- .trackJoinFailure(productId, this.currentGroupId, errCode, errMsg)
6734
+ .trackJoinAttempt(productId, joinTargetGroupId)
5850
6735
  .catch((e) => this.logger.warn("Analytics tracking failed", e));
5851
6736
  }
5852
- this.setButtonLoadingState(false);
5853
- return; // Don't open modal on error
6737
+ let joinResponse = await this.apiClient.joinGroup(joinTargetGroupId);
6738
+ if (!joinResponse.success && recoveryGroupId && this.currentGroupId !== recoveryGroupId) {
6739
+ this.clearStoredRecoveryGroupId(productId);
6740
+ joinResponse = await this.apiClient.joinGroup(this.currentGroupId);
6741
+ }
6742
+ if (!joinResponse.success) {
6743
+ this.logger.error("Failed to join group, modal will not open", joinResponse.error);
6744
+ if (this.analyticsClient) {
6745
+ const errCode = (_e = joinResponse.error) === null || _e === void 0 ? void 0 : _e.code;
6746
+ const errMsg = (_f = joinResponse.error) === null || _f === void 0 ? void 0 : _f.message;
6747
+ this.analyticsClient
6748
+ .trackJoinFailure(productId, joinTargetGroupId, errCode, errMsg)
6749
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
6750
+ }
6751
+ this.setButtonLoadingState(false);
6752
+ return; // Don't open modal on error
6753
+ }
6754
+ groupJoinData = joinResponse.data;
6755
+ if ((_g = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _g === void 0 ? void 0 : _g.id) {
6756
+ this.currentGroupId = groupJoinData.group.id;
6757
+ }
6758
+ if (recoveryGroupId && ((_h = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _h === void 0 ? void 0 : _h.id)) {
6759
+ this.clearStoredRecoveryGroupId(productId);
6760
+ if (groupJoinData.group.id !== recoveryGroupId) {
6761
+ this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
6762
+ }
6763
+ else {
6764
+ this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
6765
+ }
6766
+ }
5854
6767
  }
5855
- groupJoinData = joinResponse.data;
5856
6768
  this.logger.info("Successfully joined group", groupJoinData);
5857
6769
  if (this.analyticsClient && groupJoinData) {
5858
6770
  this.analyticsClient
@@ -5865,26 +6777,39 @@ class WidgetRoot {
5865
6777
  isValidOfflineRedemption(groupJoinData.offline_redemption)
5866
6778
  ? groupJoinData.offline_redemption
5867
6779
  : null;
6780
+ const joinedGroupId = (_j = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _j === void 0 ? void 0 : _j.id;
5868
6781
  // Trigger invite tracking before opening lobby (global for product)
5869
- try {
5870
- const inviteResponse = await this.apiClient.inviteToGroup(this.currentGroupId, "copy_link");
5871
- if (inviteResponse.success && inviteResponse.data) {
5872
- inviteData = inviteResponse.data;
5873
- this.logger.info("Invite link generated", inviteData);
6782
+ if (joinedGroupId) {
6783
+ try {
6784
+ const inviteResponse = await this.apiClient.inviteToGroup(joinedGroupId, "copy_link");
6785
+ if (inviteResponse.success && inviteResponse.data) {
6786
+ inviteData = inviteResponse.data;
6787
+ this.logger.info("Invite link generated", inviteData);
6788
+ }
6789
+ else {
6790
+ this.logger.warn("Invite link generation failed", inviteResponse.error);
6791
+ }
5874
6792
  }
5875
- else {
5876
- this.logger.warn("Invite link generation failed", inviteResponse.error);
6793
+ catch (inviteError) {
6794
+ this.logger.warn("Invite request failed", inviteError);
5877
6795
  }
5878
6796
  }
5879
- catch (inviteError) {
5880
- this.logger.warn("Invite request failed", inviteError);
5881
- }
5882
6797
  }
5883
6798
  catch (error) {
5884
6799
  this.logger.error("Group join failed, modal will not open", error);
6800
+ if (this.analyticsClient && recoveryGroupId) {
6801
+ this.analyticsClient
6802
+ .trackEvent("RECOVERY_FAILED", productId, {
6803
+ previousGroupId: recoveryGroupId,
6804
+ errorCode: "EXCEPTION",
6805
+ errorMessage: error instanceof Error ? error.message : String(error),
6806
+ protectionState: "protected",
6807
+ })
6808
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
6809
+ }
5885
6810
  if (this.analyticsClient) {
5886
6811
  this.analyticsClient
5887
- .trackJoinFailure(productId, (_e = this.currentGroupId) !== null && _e !== void 0 ? _e : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
6812
+ .trackJoinFailure(productId, (_k = this.currentGroupId) !== null && _k !== void 0 ? _k : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
5888
6813
  .catch((e) => this.logger.warn("Analytics tracking failed", e));
5889
6814
  }
5890
6815
  this.setButtonLoadingState(false);
@@ -5901,7 +6826,7 @@ class WidgetRoot {
5901
6826
  const progress = Math.round((groupJoinData.group.participants_count / groupJoinData.group.max_participants) * 100);
5902
6827
  // Format discount based on reward type
5903
6828
  let discountText = "";
5904
- if ((_g = (_f = this.currentRewardData) === null || _f === void 0 ? void 0 : _f.reward) === null || _g === void 0 ? void 0 : _g.value) {
6829
+ if ((_m = (_l = this.currentRewardData) === null || _l === void 0 ? void 0 : _l.reward) === null || _m === void 0 ? void 0 : _m.value) {
5905
6830
  const rewardType = this.currentRewardData.reward.type;
5906
6831
  const rewardValue = this.currentRewardData.reward.value;
5907
6832
  if (rewardType === "percentage" || rewardType === "cashback") {
@@ -5941,6 +6866,7 @@ class WidgetRoot {
5941
6866
  shareMessage: shareMessageFromInvite,
5942
6867
  isLocked: !isGroupFulfilled,
5943
6868
  offlineRedemption: offlineRedemptionFromJoin,
6869
+ redemptionMethod: (_o = groupJoinData.group.campaign_redemption_method) !== null && _o !== void 0 ? _o : this.campaignRedemptionMethod,
5944
6870
  onShare: this.analyticsClient
5945
6871
  ? () => {
5946
6872
  this.analyticsClient.trackShareClick(productId, groupJoinData.group.id, "other").catch((e) => this.logger.warn("Analytics tracking failed", e));
@@ -6111,8 +7037,10 @@ const API_ENDPOINTS = {
6111
7037
  PRODUCT_CONTEXT: "/v1/sdk/products/:productId/context",
6112
7038
  PRODUCT_REWARD: "/v1/sdk/products/:productId/reward",
6113
7039
  PRODUCT_PRIMARY_GROUP: "/v1/sdk/products/:productId/group/primary",
7040
+ PRODUCT_RECOVER_OR_JOIN_GROUP: "/v1/sdk/products/:productId/group/recover-or-join",
6114
7041
  // Group endpoints
6115
7042
  GROUP_JOIN: "/v1/sdk/groups/:groupId/join",
7043
+ GROUP_LEAVE: "/v1/sdk/groups/:groupId/leave",
6116
7044
  GROUP_CREATE_AND_JOIN: "/v1/sdk/groups/new/join",
6117
7045
  PRODUCT_ACTIVE_GROUPS: "/v1/sdk/products/:productId/groups/active",
6118
7046
  GROUP_INVITE: "/v1/sdk/groups/:groupId/invite",
@@ -7203,6 +8131,75 @@ class ApiClient {
7203
8131
  },
7204
8132
  };
7205
8133
  }
8134
+ /**
8135
+ * Leave an active group for the current SDK session.
8136
+ */
8137
+ async leaveGroup(groupId, params) {
8138
+ const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
8139
+ this.logger.info(`Leaving group: ${groupId}`);
8140
+ const response = await this.post(endpoint, {
8141
+ leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "unknown",
8142
+ leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "unknown",
8143
+ });
8144
+ if (response.success) {
8145
+ const payload = response.data;
8146
+ return {
8147
+ success: true,
8148
+ data: ((payload === null || payload === void 0 ? void 0 : payload.data) || response.data),
8149
+ };
8150
+ }
8151
+ return {
8152
+ success: false,
8153
+ error: response.error || {
8154
+ message: "Failed to leave group",
8155
+ code: "LEAVE_GROUP_ERROR",
8156
+ },
8157
+ };
8158
+ }
8159
+ async recoverOrJoinGroup(productId, contact) {
8160
+ var _a;
8161
+ const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_RECOVER_OR_JOIN_GROUP, {
8162
+ productId,
8163
+ });
8164
+ this.logger.info(`Recovering or joining group for product: ${productId}`);
8165
+ const response = await this.post(endpoint, contact ? { contact } : {});
8166
+ if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
8167
+ return {
8168
+ success: true,
8169
+ data: response.data.data,
8170
+ };
8171
+ }
8172
+ return {
8173
+ success: false,
8174
+ error: response.error || {
8175
+ message: "Failed to recover or join group",
8176
+ code: "RECOVER_OR_JOIN_GROUP_ERROR",
8177
+ },
8178
+ };
8179
+ }
8180
+ /**
8181
+ * Best-effort leave signal for unload/pagehide scenarios.
8182
+ * Uses fetch keepalive so it can run while page is closing.
8183
+ */
8184
+ leaveGroupBestEffort(groupId, params) {
8185
+ if (!groupId)
8186
+ return;
8187
+ const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
8188
+ const url = `${this.baseUrl}${endpoint}`;
8189
+ const payload = JSON.stringify({
8190
+ leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "browser_close",
8191
+ leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "voluntary_no_contact",
8192
+ });
8193
+ const authHeaders = this.authStrategy.getHeaders();
8194
+ void fetch(url, {
8195
+ method: "POST",
8196
+ headers: Object.assign({ "Content-Type": "application/json", "X-CoBuy-SDK-Version": "1.0.0", "X-CoBuy-Session": this.sessionId }, authHeaders),
8197
+ body: payload,
8198
+ keepalive: true,
8199
+ }).catch((error) => {
8200
+ this.logger.debug("Best-effort leave request failed", error);
8201
+ });
8202
+ }
7206
8203
  /**
7207
8204
  * Prepare checkout for a group
7208
8205
  *
@@ -12032,15 +13029,16 @@ class SocketManager {
12032
13029
  return;
12033
13030
  const bind = (eventName, handler) => {
12034
13031
  var _a;
12035
- if (!handler)
12036
- return;
12037
13032
  (_a = this.socket) === null || _a === void 0 ? void 0 : _a.on(eventName, (payload) => {
12038
13033
  this.logger.info(`[Socket] ${eventName} payload: ${this.formatPayload(payload)}`);
12039
13034
  this.dispatchWindowEvent(eventName, payload);
12040
- handler(payload);
13035
+ if (handler) {
13036
+ handler(payload);
13037
+ }
12041
13038
  });
12042
13039
  };
12043
13040
  bind("group:member:joined", handlers.onGroupMemberJoined);
13041
+ bind("group:member:left", handlers.onGroupMemberLeft);
12044
13042
  bind("group:created", handlers.onGroupCreated);
12045
13043
  bind("group:fulfilled", handlers.onGroupFulfilled);
12046
13044
  }
@@ -12051,6 +13049,7 @@ class SocketManager {
12051
13049
  if (!this.socket)
12052
13050
  return;
12053
13051
  this.socket.off("group:member:joined");
13052
+ this.socket.off("group:member:left");
12054
13053
  this.socket.off("group:created");
12055
13054
  this.socket.off("group:fulfilled");
12056
13055
  }
@@ -13424,6 +14423,7 @@ class CoBuy {
13424
14423
  activities: options.activities,
13425
14424
  isLocked: options.isLocked,
13426
14425
  offlineRedemption: options.offlineRedemption,
14426
+ redemptionMethod: options.redemptionMethod,
13427
14427
  };
13428
14428
  // Create modal instance
13429
14429
  const modal = new LobbyModal(modalData, {
@@ -13438,7 +14438,7 @@ class CoBuy {
13438
14438
  },
13439
14439
  onCopyLink: options.onCopyLink,
13440
14440
  onShare: options.onShare,
13441
- }, this.apiClient, this.socketManager, config.debug);
14441
+ }, this.apiClient, this.socketManager, this.analyticsClient, config.debug);
13442
14442
  // Store in map for persistence
13443
14443
  this.modals.set(modalKey, modal);
13444
14444
  // Maintain backward compatibility