`; document.body.appendChild(el); return el; } const bubble = ensureBubble(); const elH = bubble.querySelector("#tt-title"); const elB = bubble.querySelector("#tt-body"); const elClose = bubble.querySelector(".tt-close"); // ---------------- Parse [[term|heading|body]] anywhere ---------------- const TOKEN_RE = /\[\[([^|\]]+)\|([^|\]]+)\|([^\]]+)\]\]/g; const BLOCK_SKIP = new Set(["SCRIPT","STYLE","NOSCRIPT","TEXTAREA","INPUT","SELECT","CODE","PRE","TEMPLATE","IFRAME"]); function shouldSkipTextNode(n){ let el = n.parentElement; while (el){ if (BLOCK_SKIP.has(el.tagName) || el.isContentEditable) return true; el = el.parentElement; } return false; } const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); const textNodes = []; while (walker.nextNode()){ const n = walker.currentNode; if (!n.nodeValue || shouldSkipTextNode(n)) continue; if (TOKEN_RE.test(n.nodeValue)) textNodes.push(n); TOKEN_RE.lastIndex = 0; } textNodes.forEach(node => { const frag = document.createDocumentFragment(); const insideLink = !!node.parentElement.closest("a"); let text = node.nodeValue, last = 0; TOKEN_RE.lastIndex = 0; let m; while ((m = TOKEN_RE.exec(text))){ if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index))); const term=m[1].trim(), heading=m[2].trim(), body=m[3].trim(); const t = insideLink ? document.createElement("span") : document.createElement("button"); if (insideLink){ t.setAttribute("role","button"); t.setAttribute("tabindex","0"); } else { t.type="button"; } t.className="tt-trigger"; t.textContent=term; t.setAttribute("data-tt-h", heading); t.setAttribute("data-tt-b", body); t.setAttribute("aria-haspopup","dialog"); t.setAttribute("aria-expanded","false"); frag.appendChild(t); last = TOKEN_RE.lastIndex; } if (last = 2) return el; el = el.parentElement; } // Fallback: nearest non-inline container el = trigger.parentElement || document.body; while (el && el !== document.body){ const d = getComputedStyle(el).display; if (d !== "inline" && d !== "contents") return el; el = el.parentElement; } return document.body; } // Utility: child of `ancestor` that contains `target` (direct child) function directChildContaining(ancestor, target){ for (const ch of ancestor.children){ if (ch === target || ch.contains(target)) return ch; } return null; } function getElementTarget(e) { // If target is already an Element, use it if (e.target instanceof Element) return e.target; // Otherwise, walk the composed/path for the first Element const path = (typeof e.composedPath === 'function') ? e.composedPath() : []; for (const n of path) if (n instanceof Element) return n; return null; } // ---------------- Dim everything except the trigger branch (sibling branches only) ---------------- function dimAllOtherBranches(container, trigger){ undim(); // clear previous const dimEls = []; const wrappedTexts = []; const pathEls = []; // Build ELEMENT-only path [container -> ... -> trigger] const path = []; for (let el = trigger; el && el !== container; el = el.parentElement) path.push(el); path.push(container); path.reverse(); // At each ancestor level, find the *direct* child that leads to the trigger for (let i = 0; i { if (node.nodeType !== 3) return; // text only if (!node.nodeValue || !node.nodeValue.trim()) return; // If this text node sits inside branchChild, skip if (branchChild && branchChild.contains && branchChild.contains(node)) return; const span = document.createElement("span"); span.style.transition = `opacity ${DIM_EASE_MS}ms ease`; span.style.opacity = String(DIM_OPACITY); span.textContent = node.nodeValue; node.parentNode.replaceChild(span, node); wrappedTexts.push(span); }); // Keep a reference to the path elements (so we can explicitly restore opacity if needed) if (anc && anc.nodeType === 1) pathEls.push(anc); } // Hard-guard: explicitly set opacity:1 on the entire path to neutralize any inherited fade pathEls.forEach(el => { el.style.opacity = "1"; }); dimCtx = { container, dimEls, wrappedTexts, pathEls }; } function undim(){ if (!dimCtx) return; const { dimEls, wrappedTexts, pathEls } = dimCtx; // Animate back dimEls.forEach(el => { el.style.transition = `opacity ${DIM_EASE_MS}ms ease`; el.style.opacity = "1"; // remove inline style after the animation so we don't override site CSS setTimeout(() => { if (el) el.style.opacity = ""; }, DIM_EASE_MS + 50); }); wrappedTexts.forEach(span => { span.style.transition = `opacity ${DIM_EASE_MS}ms ease`; span.style.opacity = "1"; span.addEventListener("transitionend", () => { if (!span.parentNode) return; span.parentNode.replaceChild(document.createTextNode(span.textContent || ""), span); }, { once:true }); }); // Clear hard-guard on path pathEls.forEach(el => { if (el) el.style.opacity = ""; }); dimCtx = null; } // ---------------- Positioning (centered, edge-aware, flip) ---------------- function clamp(v,min,max){ return Math.max(min,Math.min(max,v)); } function measureBubbleForPlacement(){ const wasOpen = bubble.classList.contains("is-open"); if (!wasOpen){ bubble.style.visibility="hidden"; bubble.classList.add("is-open"); } const rect = bubble.getBoundingClientRect(); if (!wasOpen){ bubble.classList.remove("is-open"); bubble.style.visibility=""; } return { w: rect.width, h: rect.height }; } function placeAnchored(trigger){ const vw=innerWidth, vh=innerHeight; const r = trigger.getBoundingClientRect(); const { w, h } = measureBubbleForPlacement(); let left = r.left + (r.width/2) - (w/2); left = clamp(left, EDGE_PADDING, Math.max(EDGE_PADDING, vw - EDGE_PADDING - w)); const topBelow = r.bottom + OFFSET_Y; const spaceBelow = vh - topBelow - EDGE_PADDING; const placeBelow = spaceBelow >= h; let top = placeBelow ? topBelow : (r.top - h - OFFSET_Y); top = clamp(top, EDGE_PADDING, Math.max(EDGE_PADDING, vh - EDGE_PADDING - h)); bubble.style.left = left + "px"; bubble.style.top = top + "px"; const br = bubble.getBoundingClientRect(); if (br.bottom > vh - EDGE_PADDING){ bubble.style.maxHeight = (vh - 2*EDGE_PADDING) + "px"; bubble.style.overflowY = "auto"; } else { bubble.style.maxHeight = "none"; bubble.style.overflowY = "visible"; } } // ---------------- Open / Close (place → fade/scale) ---------------- function animateIn(){ bubble.style.transition = "none"; bubble.style.opacity = "0"; bubble.style.transform = "scale(0.95)"; void bubble.offsetWidth; bubble.style.transition = "opacity .18s ease, transform .18s ease"; bubble.style.opacity = "1"; bubble.style.transform = "scale(1)"; } function animateOut(done){ bubble.style.transition = "opacity .16s ease, transform .16s ease"; bubble.style.opacity = "0"; bubble.style.transform = "scale(0.95)"; const end = () => { bubble.removeEventListener("transitionend", end); done && done(); }; bubble.addEventListener("transitionend", end); setTimeout(end, 260); } function openFromTrigger(trigger){ if (current && current !== trigger) forceClose(); current = trigger; trigger.setAttribute("aria-expanded","true"); elH.textContent = trigger.getAttribute("data-tt-h") || ""; elB.textContent = trigger.getAttribute("data-tt-b") || ""; bubble.classList.add("is-open"); bubble.setAttribute("aria-hidden","false"); placeAnchored(trigger); animateIn(); const container = findTextContainer(trigger); dimAllOtherBranches(container, trigger); hoverCount = 0; cancelCloseTimer(); } function forceClose(){ if (!current) return; bubble.classList.remove("is-open"); bubble.setAttribute("aria-hidden","true"); current.setAttribute("aria-expanded","false"); current = null; undim(); hoverCount = 0; cancelCloseTimer(); } function closeWithAnim(){ if (!current) return; const t = current; animateOut(() => { bubble.classList.remove("is-open"); bubble.setAttribute("aria-hidden","true"); t.setAttribute("aria-expanded","false"); current = null; undim(); }); } function scheduleClose(){ cancelCloseTimer(); closeTimer = setTimeout(() => { if (hoverCount { if (isCoarse()) return; const target = getElementTarget(e); if (!target) return; const t = target.closest(".tt-trigger"); if (!t) return; onZoneEnter(); if (!current || current !== t) openFromTrigger(t); }; const handleLeave = (e) => { if (isCoarse()) return; const target = getElementTarget(e); if (!target) return; const t = target.closest(".tt-trigger"); if (!t) return; onZoneLeave(); }; document.addEventListener("pointerenter", handleEnter, true); document.addEventListener("mouseenter", handleEnter, true); document.addEventListener("pointerleave", handleLeave, true); document.addEventListener("mouseleave", handleLeave, true); // ---------------- Keyboard ---------------- document.addEventListener("focusin", (e) => { if (!e.target) return; const t = e.target.closest(".tt-trigger"); if (t) openFromTrigger(t); }); document.addEventListener("focusout", (e) => { if (!e.target) return; const t = e.target.closest(".tt-trigger"); if (t && current === t) closeWithAnim(); }); // ---------------- Mobile / coarse ---------------- document.addEventListener("pointerdown", (e) => { if (!isCoarse()) return; const t = e.target.closest(".tt-trigger"); if (!t) return; e.preventDefault(); e.stopPropagation(); if (current === t && bubble.classList.contains("is-open")) { closeWithAnim(); return; } openFromTrigger(t); }, true); document.addEventListener("click", (e) => { if (!isCoarse()) return; if (!bubble.classList.contains("is-open")) return; const inBubble = !!e.target.closest(".tt-bubble"); const onTrigger = !!e.target.closest(".tt-trigger"); if (!inBubble && !onTrigger) closeWithAnim(); }, true); // Close button + ESC elClose.addEventListener("click", closeWithAnim); document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeWithAnim(); }); // Reposition on resize/scroll while open const reposition = () => { if (!current) return; placeAnchored(current); }; addEventListener("resize", reposition, { passive: true }); addEventListener("scroll", reposition, { passive: true }); });

StubHub が Claude でライブイベントチケット販売を変革

Claude を試す
営業担当者に問い合わせる
業種:
エンターテインメント
会社の規模:
大規模
製品:
Claude Platform
所在地:
北アメリカ
20 分
からほぼ即時の応答へ、カスタマーの待ち時間を短縮
30% のコスト
カスタマーサービス運用の削減

詳細を読む
Next

動画のキャプション
Next

詳細を読む
Next

動画のキャプション
Next

前へ
Next

StubHub は、世界最大のライブイベントマーケットプレイスとして、Claude を活用して複雑なチケットの問題を数秒で解決する即時かつ正確なサポートを提供し、カスタマーエクスペリエンスを変革しています。満足度スコアを向上させながら、運用効率も最適化しています。

Claude による主な成果:

  • カスタマーの待ち時間が 20 分以上からほぼ即時の応答に短縮
  • 応答品質を向上させながら、カスタマーサービス運用コストを 30% 以上削減
  • Claude が解決率とカスタマー満足度において他の AI モデルを一貫して上回る

時間にシビアなチケット販売の要求に応える

StubHub は世界最大のライブイベントのデスティネーションです。世界中のコンサート、試合、ショーのチケットを検索したことがある方なら、StubHub にたどり着いたことがあるでしょう。

「私たちは、チケットプラットフォームの意味を再定義しています。私たちを差別化するのは、テクノロジーを活用して複雑なカスタマージャーニーをシンプルで楽しい体験に変える能力です」と StubHub のエンジニアリング部門チーフオブスタッフである Timothy Addison 氏は述べています。

数百万人のファンにサービスを提供することは、独自の運用上の課題、すなわち大規模なカスタマーサポートに直面することを意味しました。従来の e コマースとは異なり、チケット販売は容赦のない締め切りで運営されます。カスタマーがサポートに連絡するとき、多くの場合イベントまで数時間または数分しかなく、応答の遅れがコンサートの見逃しや特別な機会の台無しを意味する可能性があります。

従来のサポートモデルではこれらの要求に応えることができませんでした。StubHub は、ますます複雑化する注文追跡、チケットアクセス、直前のポリシーに関する問題に対応しながら、卓越したサービス品質を維持しつつ応答時間を劇的に短縮する必要がありました。

データ駆動のテストで Claude を選定

StubHub が Claude で構築することを決めたのは、データ駆動の結果に基づいています。チームは複数の AI モデルを使って厳密な A/B テストを実施し、主要指標でパフォーマンスを測定しました。

「Claude を選んだ決定は完全にデータ駆動でした。複数のモデルプロバイダーを並べてテストし、Claude がケース解決率とカスタマー満足度スコアで一貫して最良の結果を出しました」と Addison 氏は説明しています。

Claude が際立ったのは、繊細なカスタマーの意図を理解し、関連する内部ポリシーやコンテキストをリアルタイムで取得する能力でした。「複雑なカスタマーのリクエストを理解し、内部システムから適切な情報を即座に取得し、人間のエージェントの品質に匹敷する解決策を提供できる AI モデルが必要でした」と Addison 氏は述べています。

Claude が即時のチケットサポートを支える仕組み

Claude は RAG(検索拡張生成)パイプラインを通じて StubHub の内部システムに接続し、リアルタイムのナレッジベースデータ、カスタマー詳細、注文コンテキストを取得して包括的なサポートを提供します。StubHub の AI バーチャルアシスタントは、注文の追跡やイベント情報の取得からチケットアクセスの支援やポリシーに関する質問の解決まで、カスタマーの問題の増加分をエンドツーエンドで処理し、24 時間 365 日、あらゆる言語でカスタマーをサポートしています。

「チャットボットは、カスタマーが注文を追跡し、イベント情報を取得し、イベント前にチケットにアクセスできるよう支援します。これらのタスクは以前 20 分かかり、複数の電話が必要でした」と Addison 氏は語っています。変革は迅速に進みました。StubHub は最初のユースケースをわずか 6 週間でローンチし、Anthropic との効果的な協力を実証しました。

「それは継続的な改善ループへと発展しました。常に改善し、拡大し、エクスペリエンスを最適化し続けています」と Addison 氏は述べています。「Anthropic チームのサポート、専門知識、対応力のおかげで、迅速かつ自信を持って前進できました」

コストセンターから戦略的優位性への変革

Claude の StubHub のオペレーションへのインパクトは絶大です。Claude は人間のエージェントと協力して、迅速で正確、かつポリシーに準拠したサポートを 24 時間 365 日、あらゆる言語で大規模に提供しています。

「Claude はカスタマーサービスに対する私たちの見方を根本的に変えました。今ではカスタマー満足度を向上させる製品エクスペリエンスの中核的な部分になりつつあります」と Addison 氏は説明しています。「Claude は日常的なケースにおいて、問い合わせから解決までのカスタマージャーニー全体を処理します。これは単なる自動化を超えて、サポートエコシステム全体を効率化するものです」

カスタマーサポートは StubHub の主要なユースケースですが、同社は Claude の影響を社全体に拡大しています。エンジニアリングチームは Claude Code を試験的に導入し、開発者がコードの作成、編集、修正を行えるようにしています。これは、従業員の生産性を向上させながらカスタマーエクスペリエンスを変革する、StubHub の二重の AI アプローチを示しています。

インテリジェントなイベント体験の未来を構築

StubHub は、AI がファンのライブイベントの発見と体験のあり方を変革すると考えています。ロードマップには、ユーザーの意図を理解する直感的な検索、AI を活用したイベント探索、ダイナミックプライシングツール、インテリジェントな購入アシスタントが含まれています。

「Anthropic とのコラボレーションがビジネス全体に拡大していくことを期待しています」と Addison 氏は語っています。「その可能性に胸が踊り、Anthropic チームとの協力を楽しんでいます。これはまだ始まりに過ぎません」

ライブイベントが世界中の人々をつなぐ中、StubHub はファンと体験を結びつけるテクノロジーがイベントそのものと同様にシームレスで楽しいものであることを保証します。Claude が AI 戦略の基盤となり、デジタル時代におけるチケットプラットフォームを再定義しています。

Next

動画のキャプション
Next

前へ
Next