@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.cjs CHANGED
@@ -6901,7 +6901,7 @@ function retryAfterCopy(details) {
6901
6901
  }
6902
6902
  function accessLine(access) {
6903
6903
  if (!access || !access.intent) return "\u2014";
6904
- 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";
6904
+ const base = access.intent === "server" ? "Website integration" : access.intent === "hosted_link" ? "Buyer link" : "Not distributed yet";
6905
6905
  const grants = access.hasActiveGrants ? "in use now" : access.lastMintAt ? `last used ${new Date(access.lastMintAt).toLocaleDateString()}` : null;
6906
6906
  const detail = access.detail ?? grants;
6907
6907
  return detail ? `${base} \xB7 ${detail}` : base;
@@ -7293,7 +7293,7 @@ var ManageApi = class {
7293
7293
  };
7294
7294
 
7295
7295
  // src/channelsMode.ts
7296
- var POLL_MS = 1e4;
7296
+ var POLL_MS = 3e4;
7297
7297
  var MAX_FLAGS = 8;
7298
7298
  var SEAT_LIST_PAGE = 300;
7299
7299
  var PREVIEW_ELIGIBLE_FILL = "#6e7bff";
@@ -7392,6 +7392,12 @@ var CHANNELS_CSS = (
7392
7392
  .slm-ch-selnum.bump{animation:slm-ch-bump var(--slm-mo-base) var(--slm-mo-spring)}
7393
7393
  .slm-ch-row2{display:flex;gap:8px;margin-top:8px}
7394
7394
  .slm-ch-row2 .slm-btn{flex:1;min-width:0}
7395
+ /* distribute options \u2014 two actions, each with the one sentence that explains it */
7396
+ .slm-ch-dist{display:flex;flex-direction:column;gap:6px;padding:12px;margin-bottom:8px;border:1px solid var(--slm-line);
7397
+ border-radius:10px;background:var(--slm-surface)}
7398
+ .slm-ch-dist b{font-size:13px;font-weight:800}
7399
+ .slm-ch-dist .why{color:var(--slm-muted);font-size:11.5px;line-height:1.45}
7400
+ .slm-ch-dist .slm-btn{width:100%;margin-top:2px}
7395
7401
  .slm-ch-alert{display:flex;align-items:flex-start;gap:9px;padding:11px 13px;border-radius:10px;font-size:12.5px;
7396
7402
  line-height:1.5;margin-bottom:12px}
7397
7403
  .slm-ch-alert.warn{background:rgba(244,183,64,.1);border:1px solid rgba(244,183,64,.4);color:#f4d58a}
@@ -7407,7 +7413,7 @@ var CHANNELS_CSS = (
7407
7413
  /* dialogs */
7408
7414
  .slm-ch-scrim{position:absolute;inset:0;z-index:12;background:rgba(4,6,12,.62);display:grid;place-items:center;
7409
7415
  padding:18px;animation:slm-ch-fade var(--slm-mo-quick) var(--slm-mo-out)}
7410
- .slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;background:#12151f;border:1px solid var(--slm-line);
7416
+ .slm-ch-dialog{width:min(460px,100%);max-height:100%;overflow:auto;overscroll-behavior:contain;background:#12151f;border:1px solid var(--slm-line);
7411
7417
  border-radius:14px;padding:20px;box-shadow:0 24px 70px rgba(0,0,0,.6);
7412
7418
  animation:slm-ch-rise var(--slm-mo-base) var(--slm-mo-out)}
7413
7419
  .slm-ch-dialog h3{margin:0 0 4px;font-size:16px;font-weight:800;letter-spacing:-.01em}
@@ -7434,7 +7440,7 @@ var CHANNELS_CSS = (
7434
7440
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
7435
7441
  .slm-ch-err{color:#f1a4a6;font-size:11.5px;margin-top:6px}
7436
7442
 
7437
- /* hosted access links \u2014 STATUS only; there is no Copy control on this card */
7443
+ /* buyer links \u2014 STATUS only; there is no Copy control on this card */
7438
7444
  .slm-ch-link{padding:10px 11px;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface);
7439
7445
  margin-bottom:8px}
7440
7446
  .slm-ch-link .lk-head{display:flex;align-items:center;gap:8px}
@@ -7453,8 +7459,8 @@ var CHANNELS_CSS = (
7453
7459
  .slm-ch-radio input{flex:none;margin-top:2px}
7454
7460
  .slm-ch-radio b{display:block;font-weight:800;margin-bottom:2px}
7455
7461
  .slm-ch-radio .why{display:block;color:var(--slm-muted);font-size:11.5px;line-height:1.45}
7456
- .slm-ch-seatlist{max-height:44vh;overflow:auto;border:1px solid var(--slm-line);border-radius:10px;
7457
- background:var(--slm-surface);margin-top:10px}
7462
+ .slm-ch-seatlist{max-height:44vh;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
7463
+ border-radius:10px;background:var(--slm-surface);margin-top:10px}
7458
7464
  .slm-ch-seatgroup{padding:8px 10px;border-bottom:1px solid var(--slm-line);display:flex;align-items:center;
7459
7465
  justify-content:space-between;gap:8px;font-size:11px;font-weight:800;color:var(--slm-muted);position:sticky;top:0;
7460
7466
  background:var(--slm-surface)}
@@ -7504,12 +7510,12 @@ function bucketRowsHtml(rows) {
7504
7510
  </div>`).join("");
7505
7511
  }
7506
7512
  var SERVER_INTEGRATION_HTML = `
7507
- <p class="slm-eyebrow" style="margin-top:18px">Server integration</p>
7508
- <p class="slm-hint">There is nothing to set up on this screen. Your own server mints a short-lived buyer
7513
+ <p class="slm-eyebrow" style="margin-top:18px">On your website</p>
7514
+ <p class="slm-hint">There is nothing more to set up on this screen. Your own server mints a short-lived buyer
7509
7515
  access session for this channel with the SeatLayer server SDK and hands it to the widget. A channel name
7510
7516
  on its own never grants access.</p>
7511
7517
  <p class="slm-note"><a class="slm-linkbtn" href="https://docs.seatlayer.io/server-api/channels"
7512
- target="_blank" rel="noreferrer noopener">Read the server integration guide \u2192</a></p>`;
7518
+ target="_blank" rel="noreferrer noopener">Read the website integration guide \u2192</a></p>`;
7513
7519
  function esc(value) {
7514
7520
  return String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
7515
7521
  }
@@ -7590,6 +7596,17 @@ var ChannelsMode = class {
7590
7596
  this.stagedDoneTimer = null;
7591
7597
  this.lastSelectionCount = 0;
7592
7598
  this.lastCounts = /* @__PURE__ */ new Map();
7599
+ /** The markup currently in the rail. An identical repaint is skipped, which is
7600
+ * what keeps the organizer's scroll position (and open <select>) alive. */
7601
+ this.railHtml = null;
7602
+ /**
7603
+ * The `assignmentVersion` the allocation map was built from. Walking every
7604
+ * allocation page is the expensive half of a refresh and the server already
7605
+ * tells us, in the channels response, whether ANY seat moved. Unchanged
7606
+ * version, unchanged allocation — so the walk is skipped entirely.
7607
+ */
7608
+ this.allocationVersion = null;
7609
+ this.onVisibility = null;
7593
7610
  this.host = host;
7594
7611
  this.caps = capabilities;
7595
7612
  }
@@ -7600,6 +7617,7 @@ var ChannelsMode = class {
7600
7617
  this.active = true;
7601
7618
  this.mapIntent = "pan";
7602
7619
  this.focusedSectionId = null;
7620
+ this.railHtml = null;
7603
7621
  this.ensureLayer();
7604
7622
  this.host.root.classList.add("ch-mode");
7605
7623
  this.applySheetClasses();
@@ -7608,8 +7626,15 @@ var ChannelsMode = class {
7608
7626
  this.onInteractionChange?.();
7609
7627
  void this.refresh();
7610
7628
  this.pollTimer = setInterval(() => {
7629
+ if (typeof document !== "undefined" && document.hidden) return;
7611
7630
  void this.refresh({ quiet: true });
7612
7631
  }, POLL_MS);
7632
+ if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
7633
+ this.onVisibility = () => {
7634
+ if (this.active && !document.hidden) void this.refresh({ quiet: true });
7635
+ };
7636
+ document.addEventListener("visibilitychange", this.onVisibility);
7637
+ }
7613
7638
  }
7614
7639
  /** Called when the cockpit leaves Channels mode. Everything this mode painted
7615
7640
  * over the map goes with it — no other tool ever inherits a channel overlay. */
@@ -7618,6 +7643,11 @@ var ChannelsMode = class {
7618
7643
  this.active = false;
7619
7644
  if (this.pollTimer) clearInterval(this.pollTimer);
7620
7645
  this.pollTimer = null;
7646
+ if (this.onVisibility && typeof document !== "undefined") {
7647
+ document.removeEventListener("visibilitychange", this.onVisibility);
7648
+ }
7649
+ this.onVisibility = null;
7650
+ this.railHtml = null;
7621
7651
  this.closeDialog({ restoreFocus: false });
7622
7652
  this.links = [];
7623
7653
  this.linksChannelId = null;
@@ -7706,8 +7736,11 @@ var ChannelsMode = class {
7706
7736
  if (!this.targetChannelId) {
7707
7737
  this.targetChannelId = list.channels.find((c) => c.state === "active")?.id ?? PUBLIC_CHANNEL_ID;
7708
7738
  }
7709
- await this.loadAllocation(seq);
7710
- if (superseded()) return;
7739
+ if (this.allocationVersion !== list.assignmentVersion) {
7740
+ await this.loadAllocation(seq);
7741
+ if (superseded()) return;
7742
+ this.allocationVersion = list.assignmentVersion;
7743
+ }
7711
7744
  if (this.detailChannelId) await this.loadLinks(this.detailChannelId);
7712
7745
  if (superseded()) return;
7713
7746
  this.loading = false;
@@ -8126,35 +8159,59 @@ var ChannelsMode = class {
8126
8159
  this.paintOverlay();
8127
8160
  }
8128
8161
  }
8162
+ /**
8163
+ * Replace the rail's markup — but only when it actually differs, and never at
8164
+ * the cost of where the organizer had scrolled to.
8165
+ *
8166
+ * The rail repaints on a clock, on every selection change and after every
8167
+ * mutation. Rewriting `innerHTML` each time resets `scrollTop`, which is
8168
+ * exactly what "the rail gets stuck" was: scroll down to Create channel, the
8169
+ * poll ticks, and the list snaps back to the top under the cursor. So skip the
8170
+ * write when the markup is byte-identical, and restore the offset when it is
8171
+ * not.
8172
+ *
8173
+ * Returns whether the DOM was rewritten. Callers must only re-wire listeners
8174
+ * when it was — a skipped paint keeps the old nodes AND their listeners, so
8175
+ * re-wiring would double every handler.
8176
+ */
8177
+ setRailHtml(html) {
8178
+ if (html === this.railHtml) return false;
8179
+ const rail = this.host.rail;
8180
+ const scrollTop = rail.scrollTop;
8181
+ rail.innerHTML = html;
8182
+ this.railHtml = html;
8183
+ if (scrollTop) rail.scrollTop = scrollTop;
8184
+ return true;
8185
+ }
8129
8186
  paintRail() {
8130
8187
  if (!this.active) return;
8131
8188
  const rail = this.host.rail;
8132
8189
  if (!this.caps.view) {
8133
- rail.innerHTML = `<p class="slm-eyebrow">Sales channels</p>
8134
- <p class="slm-hint">You need channel-management permission on this event to see allocations.</p>`;
8190
+ this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
8191
+ <p class="slm-hint">You need channel-management permission on this event to see allocations.</p>`);
8135
8192
  return;
8136
8193
  }
8137
8194
  if (this.loading && !this.list) {
8138
- rail.innerHTML = `<p class="slm-eyebrow">Sales channels</p>
8139
- <div class="slm-empty">Loading allocations\u2026</div>`;
8195
+ this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
8196
+ <div class="slm-empty">Loading allocations\u2026</div>`);
8140
8197
  return;
8141
8198
  }
8142
8199
  if (!this.list && this.loadError) {
8143
- rail.innerHTML = `<p class="slm-eyebrow">Sales channels</p>
8200
+ if (this.setRailHtml(`<p class="slm-eyebrow">Sales channels</p>
8144
8201
  <div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
8145
8202
  <span><b>Couldn't load sales channels.</b> Everything else on this event still works.
8146
- <button type="button" data-ch-act="retry">Try again</button></span></div>`;
8147
- rail.querySelector('[data-ch-act="retry"]')?.addEventListener("click", () => {
8148
- void this.refresh();
8149
- });
8203
+ <button type="button" data-ch-act="retry">Try again</button></span></div>`)) {
8204
+ rail.querySelector('[data-ch-act="retry"]')?.addEventListener("click", () => {
8205
+ void this.refresh();
8206
+ });
8207
+ }
8150
8208
  return;
8151
8209
  }
8152
8210
  const selection = this.host.selectionLabels();
8153
8211
  const grab = this.host.isCompact() ? `<div class="slm-ch-grab"><span class="slm-ch-grabbar"></span></div>` : "";
8154
8212
  const segment = this.viewSegmentHtml();
8155
8213
  const body = this.view === "preview" ? this.previewRailHtml() : this.detailChannelId ? this.detailRailHtml(this.detailChannelId) : selection.length && this.caps.manage ? this.selectionRailHtml(selection) : this.listRailHtml();
8156
- rail.innerHTML = `${grab}${segment}${body}`;
8157
- this.wireRail();
8214
+ if (this.setRailHtml(`${grab}${segment}${body}`)) this.wireRail();
8158
8215
  this.paintStagedBar();
8159
8216
  }
8160
8217
  viewSegmentHtml() {
@@ -8285,22 +8342,7 @@ var ChannelsMode = class {
8285
8342
  <button type="button" class="slm-btn ghost" data-ch-act="archive">Archive\u2026</button>
8286
8343
  </div>
8287
8344
  <p class="slm-note">Archive returns the allocation to a destination you choose. Nothing is ever deleted silently.</p>` : "";
8288
- const intent = channel.access?.intent ?? "none";
8289
- const intents = ["none", "internal", "server", "hosted_link"];
8290
- const selfServiceGap = (intent === "server" || intent === "hosted_link") && !channel.access?.hasActiveGrants;
8291
- const access = this.caps.manage ? `
8292
- <p class="slm-eyebrow" style="margin-top:14px">Buyer access</p>
8293
- <div class="slm-field">
8294
- <label for="slm-ch-intent">How should buyers reach this channel?</label>
8295
- <select class="slm-select" id="slm-ch-intent" data-ch-intent>
8296
- ${intents.map((value) => `<option value="${value}"${value === intent ? " selected" : ""}>${esc(accessIntentLabel(value))}</option>`).join("")}
8297
- </select>
8298
- </div>
8299
- <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>
8300
- ${selfServiceGap ? `<div class="slm-ch-alert warn"><span>\u26A0</span>
8301
- <span>This channel is marked for buyer self-service but no buyer has been let in yet.</span></div>` : ""}
8302
- ${this.hostedLinksHtml()}
8303
- ${SERVER_INTEGRATION_HTML}` : "";
8345
+ const access = this.caps.manage ? this.distributeHtml(channel) : "";
8304
8346
  return `
8305
8347
  <p class="slm-eyebrow">
8306
8348
  <button type="button" class="slm-linkbtn" data-ch-act="back" style="text-align:left">\u2039 All channels</button>
@@ -8310,7 +8352,54 @@ var ChannelsMode = class {
8310
8352
  ${access}
8311
8353
  ${lifecycle}`;
8312
8354
  }
8313
- // ---- hosted access links --------------------------------------------------
8355
+ /**
8356
+ * "Distribute" — how the seats in this channel actually reach a buyer.
8357
+ *
8358
+ * This replaced a four-value "access intent" picker (none / internal /
8359
+ * hosted_link / server). Two of those values did nothing anywhere: no buyer or
8360
+ * inventory path reads `access_intent`, so `none` and `internal` were labels an
8361
+ * organizer could set and then wait forever for something to happen. A third,
8362
+ * `hosted_link`, is not the organizer's to choose at all — the server sets it
8363
+ * when a buyer link is created and clears it when the last live one is revoked.
8364
+ *
8365
+ * So this is two ACTIONS, not a setting: create a buyer link, or point the
8366
+ * channel at a website integration. Both of them do something the moment they
8367
+ * are pressed. A legacy row still carrying `none` or `internal` renders the
8368
+ * neutral "not distributed yet" state with both actions offered — the stored
8369
+ * value is left alone (it is organizer-declared metadata and the API that
8370
+ * writes it is unchanged), it simply no longer has a control of its own.
8371
+ */
8372
+ distributeHtml(channel) {
8373
+ const intent = channel.access?.intent ?? "none";
8374
+ const live = channel.access?.hasActiveGrants === true;
8375
+ 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.";
8376
+ const option = (title, why, action, cta, primary) => `<div class="slm-ch-dist">
8377
+ <b>${esc(title)}</b>
8378
+ <span class="why">${esc(why)}</span>
8379
+ <button type="button" class="slm-btn${primary ? "" : " ghost"}" data-ch-act="${esc(action)}">${esc(cta)}</button>
8380
+ </div>`;
8381
+ const linksSupported = this.linksState !== "unsupported";
8382
+ return `
8383
+ <p class="slm-eyebrow" style="margin-top:14px">Distribute</p>
8384
+ <p class="slm-hint">${esc(state)}</p>
8385
+ ${linksSupported ? option(
8386
+ "Sell with a buyer link",
8387
+ "SeatLayer makes a link you send to a named group. They open it and buy only these seats.",
8388
+ "link-create",
8389
+ this.links.some(accessLinkIsLive) ? "Create another buyer link" : "Create buyer link",
8390
+ true
8391
+ ) : ""}
8392
+ ${option(
8393
+ "Integrate a website or app",
8394
+ "Your website's backend grants each buyer access \u2014 set up on the Embed page.",
8395
+ "embed-code",
8396
+ "Get embed code",
8397
+ false
8398
+ )}
8399
+ ${this.hostedLinksHtml()}
8400
+ ${intent === "server" ? SERVER_INTEGRATION_HTML : ""}`;
8401
+ }
8402
+ // ---- buyer links ----------------------------------------------------------
8314
8403
  /**
8315
8404
  * Read the status projection for the open channel. Never paints — the caller
8316
8405
  * decides when the rail repaints, so a poll-driven reload does not fight a
@@ -8340,32 +8429,34 @@ var ChannelsMode = class {
8340
8429
  }
8341
8430
  }
8342
8431
  /**
8343
- * The hosted-link section of the detail panel.
8432
+ * The buyer-link status section of the detail panel.
8344
8433
  *
8345
8434
  * STATUS ONLY, by design (comp 06 `hosted`): label, state, expiry,
8346
8435
  * redemptions, seats per buyer, live sessions. There is no Copy control here
8347
8436
  * and no field to hang one on — the URL was shown once at creation and cannot
8348
8437
  * be produced again. Rotation is the recovery path, and it says so.
8438
+ *
8439
+ * Creating a link is the Distribute card's action, not this section's, so a
8440
+ * channel with no links renders nothing here rather than an empty heading and
8441
+ * a second button saying the same thing.
8349
8442
  */
8350
8443
  hostedLinksHtml() {
8351
- const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">Hosted access links</p>`;
8444
+ const eyebrow = `<p class="slm-eyebrow" style="margin-top:18px">Buyer links</p>`;
8352
8445
  if (this.linksState === "unsupported") {
8353
8446
  return `${eyebrow}<div class="slm-ch-alert warn"><span>\u2139</span>
8354
- <span><b>Hosted links need a newer server.</b> Everything else on this channel works normally.</span></div>`;
8447
+ <span><b>Buyer links need a newer server.</b> Everything else on this channel works normally.</span></div>`;
8355
8448
  }
8356
8449
  if (this.linksState === "error") {
8357
8450
  return `${eyebrow}<div class="slm-ch-alert err" role="alert"><span>\u26A0</span>
8358
8451
  <span><b>Couldn't load this channel's links.</b>
8359
8452
  <button type="button" data-ch-act="link-reload">Try again</button></span></div>`;
8360
8453
  }
8361
- const live = this.links.filter(accessLinkIsLive).length;
8362
- 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
8363
- they open the link and buy only these seats.</p>`;
8364
- const create = this.caps.manage ? `<button type="button" class="slm-btn" style="width:100%" data-ch-act="link-create">
8365
- ${live ? "Create another hosted link" : "Create hosted access link"}</button>` : "";
8454
+ if (this.linksState === "loading" && !this.links.length) {
8455
+ return `${eyebrow}<div class="slm-empty">Loading links\u2026</div>`;
8456
+ }
8457
+ if (!this.links.length) return "";
8366
8458
  return `${eyebrow}
8367
- ${cards}
8368
- ${create}
8459
+ ${this.links.map((link) => this.linkCardHtml(link)).join("")}
8369
8460
  <p class="slm-note">A link is shown once, when you create it. SeatLayer keeps only a fingerprint of it, so it
8370
8461
  can never be shown again \u2014 if a link is lost, rotate it and send the fresh one.</p>`;
8371
8462
  }
@@ -8384,7 +8475,7 @@ var ChannelsMode = class {
8384
8475
  </div>` : "";
8385
8476
  return `<div class="slm-ch-link">
8386
8477
  <span class="lk-head">
8387
- <span class="lk-name">${esc(link.label || "Hosted link")}</span>
8478
+ <span class="lk-name">${esc(link.label || "Buyer link")}</span>
8388
8479
  <span class="slm-ch-badge ${badge.kind}">${esc(badge.text)}</span>
8389
8480
  </span>
8390
8481
  <div class="slm-ch-meter" role="img"
@@ -8487,10 +8578,6 @@ var ChannelsMode = class {
8487
8578
  this.previewIncludePublic = includePublic.checked;
8488
8579
  void this.loadPreview();
8489
8580
  });
8490
- const intent = rail.querySelector("[data-ch-intent]");
8491
- intent?.addEventListener("change", () => {
8492
- void this.setAccessIntent(intent.value);
8493
- });
8494
8581
  const grab = rail.querySelector(".slm-ch-grab");
8495
8582
  grab?.addEventListener("click", () => this.cycleDetent());
8496
8583
  rail.querySelectorAll("[data-ch-act]").forEach((button) => {
@@ -8535,6 +8622,13 @@ var ChannelsMode = class {
8535
8622
  case "link-create":
8536
8623
  this.openDialog({ kind: "linkCreate", channelId: this.detailChannelId });
8537
8624
  break;
8625
+ // The one thing this screen can actually do for a website integration is
8626
+ // record that the channel is meant for one — which is exactly the flag the
8627
+ // dashboard's Embed page reads before it offers the snippet. Saying so is
8628
+ // honest; a fake "copy code" button on a screen with no code would not be.
8629
+ case "embed-code":
8630
+ void this.chooseWebsiteIntegration();
8631
+ break;
8538
8632
  case "link-reload":
8539
8633
  if (this.detailChannelId) void this.reloadLinks();
8540
8634
  break;
@@ -8875,12 +8969,27 @@ var ChannelsMode = class {
8875
8969
  });
8876
8970
  });
8877
8971
  }
8972
+ /**
8973
+ * "Get embed code" — mark the channel as reached through the organizer's own
8974
+ * backend. The write itself is `setChannelAccessIntent(…, 'server')`, the same
8975
+ * API the old picker called; the difference is that it is now a deliberate
8976
+ * action with a consequence the organizer is told about, rather than one of
8977
+ * four dropdown values with no observable effect.
8978
+ */
8979
+ async chooseWebsiteIntegration() {
8980
+ const channelId = this.detailChannelId;
8981
+ if (!channelId) return;
8982
+ await this.setAccessIntent("server");
8983
+ }
8878
8984
  async setAccessIntent(accessIntent) {
8879
8985
  const channelId = this.detailChannelId;
8880
8986
  if (!channelId || !this.caps.manage) return;
8881
8987
  try {
8882
8988
  await this.host.api.setChannelAccessIntent(this.host.eventKey, channelId, accessIntent);
8883
8989
  await this.refresh();
8990
+ if (accessIntent === "server") {
8991
+ this.host.toast("Marked for your website. The embed code is on the Embed page.", "ok");
8992
+ }
8884
8993
  } catch (err) {
8885
8994
  this.host.toast("Couldn't save how buyers reach this channel.", "err");
8886
8995
  this.host.onError(err);
@@ -9021,7 +9130,7 @@ var ChannelsMode = class {
9021
9130
  });
9022
9131
  });
9023
9132
  }
9024
- // ---- hosted-link dialogs --------------------------------------------------
9133
+ // ---- buyer-link dialogs ---------------------------------------------------
9025
9134
  async reloadLinks() {
9026
9135
  const channelId = this.detailChannelId;
9027
9136
  if (!channelId) return;
@@ -9064,7 +9173,7 @@ var ChannelsMode = class {
9064
9173
  return;
9065
9174
  }
9066
9175
  this.renderScrim(`
9067
- <h3 id="slm-ch-dlg-title">Create a hosted access link for ${esc(channel.name)}</h3>
9176
+ <h3 id="slm-ch-dlg-title">Create a buyer link for ${esc(channel.name)}</h3>
9068
9177
  <p class="sub">Anyone who opens the link can buy from this channel's allocation \u2014 and only from it.
9069
9178
  You'll see the link once, right after you create it.</p>
9070
9179
  <div class="slm-field">
@@ -9189,7 +9298,7 @@ var ChannelsMode = class {
9189
9298
  copy?.addEventListener("click", () => {
9190
9299
  const ok = () => {
9191
9300
  copy.textContent = "Copied";
9192
- this.announce("Hosted access link copied.");
9301
+ this.announce("Buyer link copied.");
9193
9302
  };
9194
9303
  const clipboard = typeof navigator === "undefined" ? null : navigator.clipboard;
9195
9304
  if (clipboard?.writeText) {
@@ -9199,7 +9308,7 @@ var ChannelsMode = class {
9199
9308
  selectSecret(dialog);
9200
9309
  });
9201
9310
  });
9202
- this.announce("Your hosted access link is ready and is shown once.");
9311
+ this.announce("Your buyer link is ready and is shown once.");
9203
9312
  }
9204
9313
  /**
9205
9314
  * Rotate. The organizer must SAY what happens to the buyers already inside —
@@ -9218,7 +9327,7 @@ var ChannelsMode = class {
9218
9327
  <span><b>${sessions.toLocaleString()} buyer${sessions === 1 ? "" : "s"}</b> got in with the current link
9219
9328
  and still ${sessions === 1 ? "has" : "have"} active access.</span></div>` : "";
9220
9329
  this.renderScrim(`
9221
- <h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "hosted")} link?</h3>
9330
+ <h3 id="slm-ch-dlg-title">Rotate the ${esc(link.label || "buyer")} link?</h3>
9222
9331
  <p class="sub">The current link stops opening immediately and cannot be restored. You will get a new
9223
9332
  link to copy \u2014 shown once.</p>
9224
9333
  ${warning}
@@ -9277,7 +9386,7 @@ var ChannelsMode = class {
9277
9386
  }
9278
9387
  const sessions = link.activeSessions ?? 0;
9279
9388
  this.renderScrim(`
9280
- <h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "hosted")} link?</h3>
9389
+ <h3 id="slm-ch-dlg-title">Revoke the ${esc(link.label || "buyer")} link?</h3>
9281
9390
  <p class="sub">It stops opening immediately and cannot be restored \u2014 there is no undo, and no way to
9282
9391
  bring the same URL back. Seats already bought through it keep their sale.</p>
9283
9392
  ${sessions ? `<label class="slm-ch-radio">
@@ -9394,7 +9503,12 @@ var LEGEND = [
9394
9503
  var MANAGER_CSS = (
9395
9504
  /* @sl-css */
9396
9505
  `
9397
- .slm{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:480px;overflow:hidden;
9506
+ /* The floor was 480px, which trapped the cockpit in any host shorter than that:
9507
+ every ancestor here is overflow:hidden, so the bottom of the rail (Create
9508
+ channel, Show archived) was rendered but clipped away by the host and could
9509
+ not be reached by scrolling anything. 320px still keeps an unsized host from
9510
+ collapsing to the top bar, and the rail scrolls to its full content below. */
9511
+ .slm{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-height:320px;overflow:hidden;
9398
9512
  background:var(--slm-bg);color:var(--slm-text);font-family:var(--slm-font);border-radius:var(--slm-radius);
9399
9513
  /* Motion tokens (motion-system \xA72), declared by the cockpit ROOT rather than
9400
9514
  borrowed from CHANNELS_CSS. The base cockpit animates whether or not
@@ -9462,7 +9576,7 @@ var MANAGER_CSS = (
9462
9576
  .slm-liveeventdot{width:8px;height:8px;border-radius:50%;flex:none}.slm-liveeventcopy{min-width:0;overflow:hidden;text-overflow:ellipsis;
9463
9577
  white-space:nowrap;font-size:12px;font-weight:800}.slm-liveeventhint{color:var(--slm-muted);font-size:10px;white-space:nowrap}
9464
9578
  .slm-rail{width:320px;flex:none;border-left:1px solid var(--slm-line);display:flex;flex-direction:column;min-height:0}
9465
- .slm-railscroll{flex:1;overflow-y:auto;padding:16px}
9579
+ .slm-railscroll{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px}
9466
9580
  .slm-eyebrow{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--slm-muted);font-weight:800;margin-bottom:6px}
9467
9581
  .slm-hint{font-size:12.5px;color:var(--slm-muted);line-height:1.5;margin-bottom:14px}
9468
9582
 
@@ -9527,7 +9641,8 @@ var MANAGER_CSS = (
9527
9641
  color:var(--slm-muted);font-size:10.5px}
9528
9642
  .slm-linkbtn{font-size:11px!important;font-weight:800!important;color:var(--slm-accent)!important;text-align:right}
9529
9643
  .slm-linkbtn:disabled{opacity:.45;cursor:not-allowed}
9530
- .slm-blockedlist{max-height:246px;overflow:auto;border:1px solid var(--slm-line);border-radius:10px;background:var(--slm-surface)}
9644
+ .slm-blockedlist{max-height:246px;overflow:auto;overscroll-behavior:contain;border:1px solid var(--slm-line);
9645
+ border-radius:10px;background:var(--slm-surface)}
9531
9646
  .slm-blockeditem{display:grid!important;grid-template-columns:18px minmax(0,1fr);width:100%;gap:8px;padding:8px 9px!important;
9532
9647
  border-bottom:1px solid var(--slm-line)!important;text-align:left!important}
9533
9648
  .slm-blockeditem:last-child{border-bottom:0!important}.slm-blockeditem:hover{background:rgba(255,255,255,.035)!important}