@seatlayer/js 0.41.0 → 0.42.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.
package/dist/index.js CHANGED
@@ -6493,7 +6493,7 @@ function retryAfterCopy(details) {
6493
6493
  }
6494
6494
  function accessLine(access) {
6495
6495
  if (!access || !access.intent) return "\u2014";
6496
- const base = access.intent === "internal" ? "Internal selling \xB7 no buyer access needed" : access.intent === "server" ? "Server integration" : access.intent === "hosted_link" ? "Hosted access link" : "No buyer access configured";
6496
+ const base = access.intent === "server" ? "Website integration" : access.intent === "hosted_link" ? "Buyer link" : "Not distributed yet";
6497
6497
  const grants = access.hasActiveGrants ? "in use now" : access.lastMintAt ? `last used ${new Date(access.lastMintAt).toLocaleDateString()}` : null;
6498
6498
  const detail = access.detail ?? grants;
6499
6499
  return detail ? `${base} \xB7 ${detail}` : base;
@@ -6885,7 +6885,7 @@ var ManageApi = class {
6885
6885
  };
6886
6886
 
6887
6887
  // src/channelsMode.ts
6888
- var POLL_MS = 1e4;
6888
+ var POLL_MS = 3e4;
6889
6889
  var MAX_FLAGS = 8;
6890
6890
  var SEAT_LIST_PAGE = 300;
6891
6891
  var PREVIEW_ELIGIBLE_FILL = "#6e7bff";
@@ -6984,6 +6984,12 @@ var CHANNELS_CSS = (
6984
6984
  .slm-ch-selnum.bump{animation:slm-ch-bump var(--slm-mo-base) var(--slm-mo-spring)}
6985
6985
  .slm-ch-row2{display:flex;gap:8px;margin-top:8px}
6986
6986
  .slm-ch-row2 .slm-btn{flex:1;min-width:0}
6987
+ /* distribute options \u2014 two actions, each with the one sentence that explains it */
6988
+ .slm-ch-dist{display:flex;flex-direction:column;gap:6px;padding:12px;margin-bottom:8px;border:1px solid var(--slm-line);
6989
+ border-radius:10px;background:var(--slm-surface)}
6990
+ .slm-ch-dist b{font-size:13px;font-weight:800}
6991
+ .slm-ch-dist .why{color:var(--slm-muted);font-size:11.5px;line-height:1.45}
6992
+ .slm-ch-dist .slm-btn{width:100%;margin-top:2px}
6987
6993
  .slm-ch-alert{display:flex;align-items:flex-start;gap:9px;padding:11px 13px;border-radius:10px;font-size:12.5px;
6988
6994
  line-height:1.5;margin-bottom:12px}
6989
6995
  .slm-ch-alert.warn{background:rgba(244,183,64,.1);border:1px solid rgba(244,183,64,.4);color:#f4d58a}
@@ -6999,7 +7005,7 @@ var CHANNELS_CSS = (
6999
7005
  /* dialogs */
7000
7006
  .slm-ch-scrim{position:absolute;inset:0;z-index:12;background:rgba(4,6,12,.62);display:grid;place-items:center;
7001
7007
  padding:18px;animation:slm-ch-fade var(--slm-mo-quick) var(--slm-mo-out)}
7002
- .slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;background:#12151f;border:1px solid var(--slm-line);
7008
+ .slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;overscroll-behavior:contain;background:#12151f;border:1px solid var(--slm-line);
7003
7009
  border-radius:14px;padding:20px;box-shadow:0 24px 70px rgba(0,0,0,.6);
7004
7010
  animation:slm-ch-rise var(--slm-mo-base) var(--slm-mo-out)}
7005
7011
  .slm-ch-dialog h3{margin:0 0 4px;font-size:16px;font-weight:800;letter-spacing:-.01em}
@@ -7026,7 +7032,7 @@ var CHANNELS_CSS = (
7026
7032
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
7027
7033
  .slm-ch-err{color:#f1a4a6;font-size:11.5px;margin-top:6px}
7028
7034
 
7029
- /* hosted access links \u2014 STATUS only; there is no Copy control on this card */
7035
+ /* buyer links \u2014 STATUS only; there is no Copy control on this card */
7030
7036
  .slm-ch-link{padding:10px 11px;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface);
7031
7037
  margin-bottom:8px}
7032
7038
  .slm-ch-link .lk-head{display:flex;align-items:center;gap:8px}
@@ -7045,8 +7051,8 @@ var CHANNELS_CSS = (
7045
7051
  .slm-ch-radio input{flex:none;margin-top:2px}
7046
7052
  .slm-ch-radio b{display:block;font-weight:800;margin-bottom:2px}
7047
7053
  .slm-ch-radio .why{display:block;color:var(--slm-muted);font-size:11.5px;line-height:1.45}
7048
- .slm-ch-seatlist{max-height:44vh;overflow:auto;border:1px solid var(--slm-line);border-radius:10px;
7049
- background:var(--slm-surface);margin-top:10px}
7054
+ .slm-ch-seatlist{max-height:44vh;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
7055
+ border-radius:10px;background:var(--slm-surface);margin-top:10px}
7050
7056
  .slm-ch-seatgroup{padding:8px 10px;border-bottom:1px solid var(--slm-line);display:flex;align-items:center;
7051
7057
  justify-content:space-between;gap:8px;font-size:11px;font-weight:800;color:var(--slm-muted);position:sticky;top:0;
7052
7058
  background:var(--slm-surface)}
@@ -7096,12 +7102,12 @@ function bucketRowsHtml(rows) {
7096
7102
  </div>`).join("");
7097
7103
  }
7098
7104
  var SERVER_INTEGRATION_HTML = `
7099
- <p class="slm-eyebrow" style="margin-top:18px">Server integration</p>
7100
- <p class="slm-hint">There is nothing to set up on this screen. Your own server mints a short-lived buyer
7105
+ <p class="slm-eyebrow" style="margin-top:18px">On your website</p>
7106
+ <p class="slm-hint">There is nothing more to set up on this screen. Your own server mints a short-lived buyer
7101
7107
  access session for this channel with the SeatLayer server SDK and hands it to the widget. A channel name
7102
7108
  on its own never grants access.</p>
7103
7109
  <p class="slm-note"><a class="slm-linkbtn" href="https://docs.seatlayer.io/server-api/channels"
7104
- target="_blank" rel="noreferrer noopener">Read the server integration guide \u2192</a></p>`;
7110
+ target="_blank" rel="noreferrer noopener">Read the website integration guide \u2192</a></p>`;
7105
7111
  function esc(value) {
7106
7112
  return String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
7107
7113
  }
@@ -7182,6 +7188,17 @@ var ChannelsMode = class {
7182
7188
  this.stagedDoneTimer = null;
7183
7189
  this.lastSelectionCount = 0;
7184
7190
  this.lastCounts = /* @__PURE__ */ new Map();
7191
+ /** The markup currently in the rail. An identical repaint is skipped, which is
7192
+ * what keeps the organizer's scroll position (and open <select>) alive. */
7193
+ this.railHtml = null;
7194
+ /**
7195
+ * The `assignmentVersion` the allocation map was built from. Walking every
7196
+ * allocation page is the expensive half of a refresh and the server already
7197
+ * tells us, in the channels response, whether ANY seat moved. Unchanged
7198
+ * version, unchanged allocation — so the walk is skipped entirely.
7199
+ */
7200
+ this.allocationVersion = null;
7201
+ this.onVisibility = null;
7185
7202
  this.host = host;
7186
7203
  this.caps = capabilities;
7187
7204
  }
@@ -7192,6 +7209,7 @@ var ChannelsMode = class {
7192
7209
  this.active = true;
7193
7210
  this.mapIntent = "pan";
7194
7211
  this.focusedSectionId = null;
7212
+ this.railHtml = null;
7195
7213
  this.ensureLayer();
7196
7214
  this.host.root.classList.add("ch-mode");
7197
7215
  this.applySheetClasses();
@@ -7200,8 +7218,15 @@ var ChannelsMode = class {
7200
7218
  this.onInteractionChange?.();
7201
7219
  void this.refresh();
7202
7220
  this.pollTimer = setInterval(() => {
7221
+ if (typeof document !== "undefined" && document.hidden) return;
7203
7222
  void this.refresh({ quiet: true });
7204
7223
  }, POLL_MS);
7224
+ if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
7225
+ this.onVisibility = () => {
7226
+ if (this.active && !document.hidden) void this.refresh({ quiet: true });
7227
+ };
7228
+ document.addEventListener("visibilitychange", this.onVisibility);
7229
+ }
7205
7230
  }
7206
7231
  /** Called when the cockpit leaves Channels mode. Everything this mode painted
7207
7232
  * over the map goes with it — no other tool ever inherits a channel overlay. */
@@ -7210,6 +7235,11 @@ var ChannelsMode = class {
7210
7235
  this.active = false;
7211
7236
  if (this.pollTimer) clearInterval(this.pollTimer);
7212
7237
  this.pollTimer = null;
7238
+ if (this.onVisibility && typeof document !== "undefined") {
7239
+ document.removeEventListener("visibilitychange", this.onVisibility);
7240
+ }
7241
+ this.onVisibility = null;
7242
+ this.railHtml = null;
7213
7243
  this.closeDialog({ restoreFocus: false });
7214
7244
  this.links = [];
7215
7245
  this.linksChannelId = null;
@@ -7298,8 +7328,11 @@ var ChannelsMode = class {
7298
7328
  if (!this.targetChannelId) {
7299
7329
  this.targetChannelId = list.channels.find((c) => c.state === "active")?.id ?? PUBLIC_CHANNEL_ID;
7300
7330
  }
7301
- await this.loadAllocation(seq);
7302
- if (superseded()) return;
7331
+ if (this.allocationVersion !== list.assignmentVersion) {
7332
+ await this.loadAllocation(seq);
7333
+ if (superseded()) return;
7334
+ this.allocationVersion = list.assignmentVersion;
7335
+ }
7303
7336
  if (this.detailChannelId) await this.loadLinks(this.detailChannelId);
7304
7337
  if (superseded()) return;
7305
7338
  this.loading = false;
@@ -7718,35 +7751,59 @@ var ChannelsMode = class {
7718
7751
  this.paintOverlay();
7719
7752
  }
7720
7753
  }
7754
+ /**
7755
+ * Replace the rail's markup — but only when it actually differs, and never at
7756
+ * the cost of where the organizer had scrolled to.
7757
+ *
7758
+ * The rail repaints on a clock, on every selection change and after every
7759
+ * mutation. Rewriting `innerHTML` each time resets `scrollTop`, which is
7760
+ * exactly what "the rail gets stuck" was: scroll down to Create channel, the
7761
+ * poll ticks, and the list snaps back to the top under the cursor. So skip the
7762
+ * write when the markup is byte-identical, and restore the offset when it is
7763
+ * not.
7764
+ *
7765
+ * Returns whether the DOM was rewritten. Callers must only re-wire listeners
7766
+ * when it was — a skipped paint keeps the old nodes AND their listeners, so
7767
+ * re-wiring would double every handler.
7768
+ */
7769
+ setRailHtml(html) {
7770
+ if (html === this.railHtml) return false;
7771
+ const rail = this.host.rail;
7772
+ const scrollTop = rail.scrollTop;
7773
+ rail.innerHTML = html;
7774
+ this.railHtml = html;
7775
+ if (scrollTop) rail.scrollTop = scrollTop;
7776
+ return true;
7777
+ }
7721
7778
  paintRail() {
7722
7779
  if (!this.active) return;
7723
7780
  const rail = this.host.rail;
7724
7781
  if (!this.caps.view) {
7725
- rail.innerHTML = `<p class="slm-eyebrow">Sales channels</p>
7726
- <p class="slm-hint">You need channel-management permission on this event to see allocations.</p>`;
7782
+ this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
7783
+ <p class="slm-hint">You need channel-management permission on this event to see allocations.</p>`);
7727
7784
  return;
7728
7785
  }
7729
7786
  if (this.loading && !this.list) {
7730
- rail.innerHTML = `<p class="slm-eyebrow">Sales channels</p>
7731
- <div class="slm-empty">Loading allocations\u2026</div>`;
7787
+ this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
7788
+ <div class="slm-empty">Loading allocations\u2026</div>`);
7732
7789
  return;
7733
7790
  }
7734
7791
  if (!this.list && this.loadError) {
7735
- rail.innerHTML = `<p class="slm-eyebrow">Sales channels</p>
7792
+ if (this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
7736
7793
  <div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
7737
7794
  <span><b>Couldn't load sales channels.</b> Everything else on this event still works.
7738
- <button type="button" data-ch-act="retry">Try again</button></span></div>`;
7739
- rail.querySelector('[data-ch-act="retry"]')?.addEventListener("click", () => {
7740
- void this.refresh();
7741
- });
7795
+ <button type="button" data-ch-act="retry">Try again</button></span></div>`)) {
7796
+ rail.querySelector('[data-ch-act="retry"]')?.addEventListener("click", () => {
7797
+ void this.refresh();
7798
+ });
7799
+ }
7742
7800
  return;
7743
7801
  }
7744
7802
  const selection = this.host.selectionLabels();
7745
7803
  const grab = this.host.isCompact() ? `<div class="slm-ch-grab"><span class="slm-ch-grabbar"></span></div>` : "";
7746
7804
  const segment = this.viewSegmentHtml();
7747
7805
  const body = this.view === "preview" ? this.previewRailHtml() : this.detailChannelId ? this.detailRailHtml(this.detailChannelId) : selection.length && this.caps.manage ? this.selectionRailHtml(selection) : this.listRailHtml();
7748
- rail.innerHTML = `${grab}${segment}${body}`;
7749
- this.wireRail();
7806
+ if (this.setRailHtml(`${grab}${segment}${body}`)) this.wireRail();
7750
7807
  this.paintStagedBar();
7751
7808
  }
7752
7809
  viewSegmentHtml() {
@@ -7877,22 +7934,7 @@ var ChannelsMode = class {
7877
7934
  <button type="button" class="slm-btn ghost" data-ch-act="archive">Archive\u2026</button>
7878
7935
  </div>
7879
7936
  <p class="slm-note">Archive returns the allocation to a destination you choose. Nothing is ever deleted silently.</p>` : "";
7880
- const intent = channel.access?.intent ?? "none";
7881
- const intents = ["none", "internal", "server", "hosted_link"];
7882
- const selfServiceGap = (intent === "server" || intent === "hosted_link") && !channel.access?.hasActiveGrants;
7883
- const access = this.caps.manage ? `
7884
- <p class="slm-eyebrow" style="margin-top:14px">Buyer access</p>
7885
- <div class="slm-field">
7886
- <label for="slm-ch-intent">How should buyers reach this channel?</label>
7887
- <select class="slm-select" id="slm-ch-intent" data-ch-intent>
7888
- ${intents.map((value) => `<option value="${value}"${value === intent ? " selected" : ""}>${esc(accessIntentLabel(value))}</option>`).join("")}
7889
- </select>
7890
- </div>
7891
- <p class="slm-hint">${channel.access?.hasActiveGrants ? "Buyer access is live. Only this audience can buy from the allocation." : "No buyer access is configured yet. The allocation is protected \u2014 it is not available to Public sale."}</p>
7892
- ${selfServiceGap ? `<div class="slm-ch-alert warn"><span>\u26A0</span>
7893
- <span>This channel is marked for buyer self-service but no buyer has been let in yet.</span></div>` : ""}
7894
- ${this.hostedLinksHtml()}
7895
- ${SERVER_INTEGRATION_HTML}` : "";
7937
+ const access = this.caps.manage ? this.distributeHtml(channel) : "";
7896
7938
  return `
7897
7939
  <p class="slm-eyebrow">
7898
7940
  <button type="button" class="slm-linkbtn" data-ch-act="back" style="text-align:left">\u2039 All channels</button>
@@ -7902,7 +7944,54 @@ var ChannelsMode = class {
7902
7944
  ${access}
7903
7945
  ${lifecycle}`;
7904
7946
  }
7905
- // ---- hosted access links --------------------------------------------------
7947
+ /**
7948
+ * "Distribute" — how the seats in this channel actually reach a buyer.
7949
+ *
7950
+ * This replaced a four-value "access intent" picker (none / internal /
7951
+ * hosted_link / server). Two of those values did nothing anywhere: no buyer or
7952
+ * inventory path reads `access_intent`, so `none` and `internal` were labels an
7953
+ * organizer could set and then wait forever for something to happen. A third,
7954
+ * `hosted_link`, is not the organizer's to choose at all — the server sets it
7955
+ * when a buyer link is created and clears it when the last live one is revoked.
7956
+ *
7957
+ * So this is two ACTIONS, not a setting: create a buyer link, or point the
7958
+ * channel at a website integration. Both of them do something the moment they
7959
+ * are pressed. A legacy row still carrying `none` or `internal` renders the
7960
+ * neutral "not distributed yet" state with both actions offered — the stored
7961
+ * value is left alone (it is organizer-declared metadata and the API that
7962
+ * writes it is unchanged), it simply no longer has a control of its own.
7963
+ */
7964
+ distributeHtml(channel) {
7965
+ const intent = channel.access?.intent ?? "none";
7966
+ const live = channel.access?.hasActiveGrants === true;
7967
+ const state = live ? intent === "server" ? "Your website is letting buyers in. Only they can buy these seats." : "A buyer link is live. Only people with that link can buy these seats." : intent === "server" ? "Set up for your website \u2014 no buyer has come through yet. Seats stay reserved." : "Not distributed yet \u2014 seats stay reserved.";
7968
+ const option = (title, why, action, cta, primary) => `<div class="slm-ch-dist">
7969
+ <b>${esc(title)}</b>
7970
+ <span class="why">${esc(why)}</span>
7971
+ <button type="button" class="slm-btn${primary ? "" : " ghost"}" data-ch-act="${esc(action)}">${esc(cta)}</button>
7972
+ </div>`;
7973
+ const linksSupported = this.linksState !== "unsupported";
7974
+ return `
7975
+ <p class="slm-eyebrow" style="margin-top:14px">Distribute</p>
7976
+ <p class="slm-hint">${esc(state)}</p>
7977
+ ${linksSupported ? option(
7978
+ "Sell with a buyer link",
7979
+ "SeatLayer makes a link you send to a named group. They open it and buy only these seats.",
7980
+ "link-create",
7981
+ this.links.some(accessLinkIsLive) ? "Create another buyer link" : "Create buyer link",
7982
+ true
7983
+ ) : ""}
7984
+ ${option(
7985
+ "Integrate a website or app",
7986
+ "Your website's backend grants each buyer access \u2014 set up on the Embed page.",
7987
+ "embed-code",
7988
+ "Get embed code",
7989
+ false
7990
+ )}
7991
+ ${this.hostedLinksHtml()}
7992
+ ${intent === "server" ? SERVER_INTEGRATION_HTML : ""}`;
7993
+ }
7994
+ // ---- buyer links ----------------------------------------------------------
7906
7995
  /**
7907
7996
  * Read the status projection for the open channel. Never paints — the caller
7908
7997
  * decides when the rail repaints, so a poll-driven reload does not fight a
@@ -7932,32 +8021,34 @@ var ChannelsMode = class {
7932
8021
  }
7933
8022
  }
7934
8023
  /**
7935
- * The hosted-link section of the detail panel.
8024
+ * The buyer-link status section of the detail panel.
7936
8025
  *
7937
8026
  * STATUS ONLY, by design (comp 06 `hosted`): label, state, expiry,
7938
8027
  * redemptions, seats per buyer, live sessions. There is no Copy control here
7939
8028
  * and no field to hang one on — the URL was shown once at creation and cannot
7940
8029
  * be produced again. Rotation is the recovery path, and it says so.
8030
+ *
8031
+ * Creating a link is the Distribute card's action, not this section's, so a
8032
+ * channel with no links renders nothing here rather than an empty heading and
8033
+ * a second button saying the same thing.
7941
8034
  */
7942
8035
  hostedLinksHtml() {
7943
- const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">Hosted access links</p>`;
8036
+ const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">Buyer links</p>`;
7944
8037
  if (this.linksState === "unsupported") {
7945
8038
  return `${eyebrow}<div class="slm-ch-alert warn"><span>\u2139</span>
7946
- <span><b>Hosted links need a newer server.</b> Everything else on this channel works normally.</span></div>`;
8039
+ <span><b>Buyer links need a newer server.</b> Everything else on this channel works normally.</span></div>`;
7947
8040
  }
7948
8041
  if (this.linksState === "error") {
7949
8042
  return `${eyebrow}<div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
7950
8043
  <span><b>Couldn't load this channel's links.</b>
7951
8044
  <button type="button" data-ch-act="link-reload">Try again</button></span></div>`;
7952
8045
  }
7953
- const live = this.links.filter(accessLinkIsLive).length;
7954
- const cards = this.linksState === "loading" && !this.links.length ? `<div class="slm-empty">Loading links\u2026</div>` : this.links.length ? this.links.map((link) => this.linkCardHtml(link)).join("") : `<p class="slm-hint">No hosted link yet. Create one to send this allocation to a named group \u2014
7955
- they open the link and buy only these seats.</p>`;
7956
- const create = this.caps.manage ? `<button type="button" class="slm-btn" style="width:100%" data-ch-act="link-create">
7957
- ${live ? "Create another hosted link" : "Create hosted access link"}</button>` : "";
8046
+ if (this.linksState === "loading" && !this.links.length) {
8047
+ return `${eyebrow}<div class="slm-empty">Loading links\u2026</div>`;
8048
+ }
8049
+ if (!this.links.length) return "";
7958
8050
  return `${eyebrow}
7959
- ${cards}
7960
- ${create}
8051
+ ${this.links.map((link) => this.linkCardHtml(link)).join("")}
7961
8052
  <p class="slm-note">A link is shown once, when you create it. SeatLayer keeps only a fingerprint of it, so it
7962
8053
  can never be shown again \u2014 if a link is lost, rotate it and send the fresh one.</p>`;
7963
8054
  }
@@ -7976,7 +8067,7 @@ var ChannelsMode = class {
7976
8067
  </div>` : "";
7977
8068
  return `<div class="slm-ch-link">
7978
8069
  <span class="lk-head">
7979
- <span class="lk-name">${esc(link.label || "Hosted link")}</span>
8070
+ <span class="lk-name">${esc(link.label || "Buyer link")}</span>
7980
8071
  <span class="slm-ch-badge ${badge.kind}">${esc(badge.text)}</span>
7981
8072
  </span>
7982
8073
  <div class="slm-ch-meter" role="img"
@@ -8079,10 +8170,6 @@ var ChannelsMode = class {
8079
8170
  this.previewIncludePublic = includePublic.checked;
8080
8171
  void this.loadPreview();
8081
8172
  });
8082
- const intent = rail.querySelector("[data-ch-intent]");
8083
- intent?.addEventListener("change", () => {
8084
- void this.setAccessIntent(intent.value);
8085
- });
8086
8173
  const grab = rail.querySelector(".slm-ch-grab");
8087
8174
  grab?.addEventListener("click", () => this.cycleDetent());
8088
8175
  rail.querySelectorAll("[data-ch-act]").forEach((button) => {
@@ -8127,6 +8214,13 @@ var ChannelsMode = class {
8127
8214
  case "link-create":
8128
8215
  this.openDialog({ kind: "linkCreate", channelId: this.detailChannelId });
8129
8216
  break;
8217
+ // The one thing this screen can actually do for a website integration is
8218
+ // record that the channel is meant for one — which is exactly the flag the
8219
+ // dashboard's Embed page reads before it offers the snippet. Saying so is
8220
+ // honest; a fake "copy code" button on a screen with no code would not be.
8221
+ case "embed-code":
8222
+ void this.chooseWebsiteIntegration();
8223
+ break;
8130
8224
  case "link-reload":
8131
8225
  if (this.detailChannelId) void this.reloadLinks();
8132
8226
  break;
@@ -8467,12 +8561,27 @@ var ChannelsMode = class {
8467
8561
  });
8468
8562
  });
8469
8563
  }
8564
+ /**
8565
+ * "Get embed code" — mark the channel as reached through the organizer's own
8566
+ * backend. The write itself is `setChannelAccessIntent(…, 'server')`, the same
8567
+ * API the old picker called; the difference is that it is now a deliberate
8568
+ * action with a consequence the organizer is told about, rather than one of
8569
+ * four dropdown values with no observable effect.
8570
+ */
8571
+ async chooseWebsiteIntegration() {
8572
+ const channelId = this.detailChannelId;
8573
+ if (!channelId) return;
8574
+ await this.setAccessIntent("server");
8575
+ }
8470
8576
  async setAccessIntent(accessIntent) {
8471
8577
  const channelId = this.detailChannelId;
8472
8578
  if (!channelId || !this.caps.manage) return;
8473
8579
  try {
8474
8580
  await this.host.api.setChannelAccessIntent(this.host.eventKey, channelId, accessIntent);
8475
8581
  await this.refresh();
8582
+ if (accessIntent === "server") {
8583
+ this.host.toast("Marked for your website. The embed code is on the Embed page.", "ok");
8584
+ }
8476
8585
  } catch (err) {
8477
8586
  this.host.toast("Couldn't save how buyers reach this channel.", "err");
8478
8587
  this.host.onError(err);
@@ -8613,7 +8722,7 @@ var ChannelsMode = class {
8613
8722
  });
8614
8723
  });
8615
8724
  }
8616
- // ---- hosted-link dialogs --------------------------------------------------
8725
+ // ---- buyer-link dialogs ---------------------------------------------------
8617
8726
  async reloadLinks() {
8618
8727
  const channelId = this.detailChannelId;
8619
8728
  if (!channelId) return;
@@ -8656,7 +8765,7 @@ var ChannelsMode = class {
8656
8765
  return;
8657
8766
  }
8658
8767
  this.renderScrim(`
8659
- <h3 id="slm-ch-dlg-title">Create a hosted access link for ${esc(channel.name)}</h3>
8768
+ <h3 id="slm-ch-dlg-title">Create a buyer link for ${esc(channel.name)}</h3>
8660
8769
  <p class="sub">Anyone who opens the link can buy from this channel's allocation \u2014 and only from it.
8661
8770
  You'll see the link once, right after you create it.</p>
8662
8771
  <div class="slm-field">
@@ -8781,7 +8890,7 @@ var ChannelsMode = class {
8781
8890
  copy?.addEventListener("click", () => {
8782
8891
  const ok = () => {
8783
8892
  copy.textContent = "Copied";
8784
- this.announce("Hosted access link copied.");
8893
+ this.announce("Buyer link copied.");
8785
8894
  };
8786
8895
  const clipboard = typeof navigator === "undefined" ? null : navigator.clipboard;
8787
8896
  if (clipboard?.writeText) {
@@ -8791,7 +8900,7 @@ var ChannelsMode = class {
8791
8900
  selectSecret(dialog);
8792
8901
  });
8793
8902
  });
8794
- this.announce("Your hosted access link is ready and is shown once.");
8903
+ this.announce("Your buyer link is ready and is shown once.");
8795
8904
  }
8796
8905
  /**
8797
8906
  * Rotate. The organizer must SAY what happens to the buyers already inside —
@@ -8810,7 +8919,7 @@ var ChannelsMode = class {
8810
8919
  <span><b>${sessions.toLocaleString()} buyer${sessions === 1 ? "" : "s"}</b> got in with the current link
8811
8920
  and still ${sessions === 1 ? "has" : "have"} active access.</span></div>` : "";
8812
8921
  this.renderScrim(`
8813
- <h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "hosted")} link?</h3>
8922
+ <h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "buyer")} link?</h3>
8814
8923
  <p class="sub">The current link stops opening immediately and cannot be restored. You will get a new
8815
8924
  link to copy \u2014 shown once.</p>
8816
8925
  ${warning}
@@ -8869,7 +8978,7 @@ var ChannelsMode = class {
8869
8978
  }
8870
8979
  const sessions = link.activeSessions ?? 0;
8871
8980
  this.renderScrim(`
8872
- <h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "hosted")} link?</h3>
8981
+ <h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "buyer")} link?</h3>
8873
8982
  <p class="sub">It stops opening immediately and cannot be restored \u2014 there is no undo, and no way to
8874
8983
  bring the same URL back. Seats already bought through it keep their sale.</p>
8875
8984
  ${sessions ? `<label class="slm-ch-radio">
@@ -8986,7 +9095,12 @@ var LEGEND = [
8986
9095
  var MANAGER_CSS = (
8987
9096
  /* @sl-css */
8988
9097
  `
8989
- .slm{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:480px;overflow:hidden;
9098
+ /* The floor was 480px, which trapped the cockpit in any host shorter than that:
9099
+ every ancestor here is overflow:hidden, so the bottom of the rail (Create
9100
+ channel, Show archived) was rendered but clipped away by the host and could
9101
+ not be reached by scrolling anything. 320px still keeps an unsized host from
9102
+ collapsing to the top bar, and the rail scrolls to its full content below. */
9103
+ .slm{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:320px;overflow:hidden;
8990
9104
  background:var(--slm-bg);color:var(--slm-text);font-family:var(--slm-font);border-radius:var(--slm-radius);
8991
9105
  /* Motion tokens (motion-system \xA72), declared by the cockpit ROOT rather than
8992
9106
  borrowed from CHANNELS_CSS. The base cockpit animates whether or not
@@ -9054,7 +9168,7 @@ var MANAGER_CSS = (
9054
9168
  .slm-liveeventdot{width:8px;height:8px;border-radius:50%;flex:none}.slm-liveeventcopy{min-width:0;overflow:hidden;text-overflow:ellipsis;
9055
9169
  white-space:nowrap;font-size:12px;font-weight:800}.slm-liveeventhint{color:var(--slm-muted);font-size:10px;white-space:nowrap}
9056
9170
  .slm-rail{width:320px;flex:none;border-left:1px solid var(--slm-line);display:flex;flex-direction:column;min-height:0}
9057
- .slm-railscroll{flex:1;overflow-y:auto;padding:16px}
9171
+ .slm-railscroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px}
9058
9172
  .slm-eyebrow{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--slm-muted);font-weight:800;margin-bottom:6px}
9059
9173
  .slm-hint{font-size:12.5px;color:var(--slm-muted);line-height:1.5;margin-bottom:14px}
9060
9174
 
@@ -9119,7 +9233,8 @@ var MANAGER_CSS = (
9119
9233
  color:var(--slm-muted);font-size:10.5px}
9120
9234
  .slm-linkbtn{font-size:11px!important;font-weight:800!important;color:var(--slm-accent)!important;text-align:right}
9121
9235
  .slm-linkbtn:disabled{opacity:.45;cursor:not-allowed}
9122
- .slm-blockedlist{max-height:246px;overflow:auto;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface)}
9236
+ .slm-blockedlist{max-height:246px;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
9237
+ border-radius:10px;background:var(--slm-surface)}
9123
9238
  .slm-blockeditem{display:grid!important;grid-template-columns:18px minmax(0,1fr);width:100%;gap:8px;padding:8px 9px!important;
9124
9239
  border-bottom:1px solid var(--slm-line)!important;text-align:left!important}
9125
9240
  .slm-blockeditem:last-child{border-bottom:0!important}.slm-blockeditem:hover{background:rgba(255,255,255,.035)!important}