PageSourceSearch

https://xagent.id/assets/demo.js?v=tenant-20260920

js xagent.id collected 2026-10-05 06:33:13 UTC 157,577 bytes, 1,763 lines download raw bytes

1(() => {
2  const AGENT_ID_LOCALES = {
3    en:{label:"English",patent:"Patent Pending",title:"xAgent ID | A verifiable Agent identity for every website",copy:{}},
4    "zh-Hant":{label:"繁體中文",patent:"專利申請中",title:"xAgent ID | 每個網站的可驗證 Agent 身分",copy:{}},
5    "zh-Hans":{label:"简体中文",patent:"专利申请中",title:"xAgent ID | 每个网站的可验证 Agent 身份",copy:{
6      "Benefits":"价值","How it works":"运作方式","Pricing":"方案","Get started":"开始使用","Technical docs":"技术文件","A verifiable Agent identityfor every website.":"让每个网站,都拥有可验证的 Agent 身份。","Let your website communicate with AI, buyers, and customers. Trusted identity comes before discovery, answers, recommendations, and actions.":"让你的网站主动与 AI、买家和客户沟通。身份先被信任,内容才有机会被找到、回答、推荐与操作。","See installation":"查看安装方式","View results":"查看成效报表","Let AI find you":"让 AI 找到你","A verified website identity makes your business easier for Agents to recognize":"网站身份通过验证后,Agent 更容易识别你的企业","Your website":"你的网站","Searchable":"可被搜索","Answerable":"可被回答","Recommendable":"可被推荐","Actionable":"可被操作","Customers now ask AI.Is your website ready?":"客户开始用 AI 找答案,你的网站准备好了吗?","Three controls.Proof the improvement is live.":"三个控制环节,确认改善真的生效。","Start with identity.Add only what your website needs.":"先建立网站身份,再按需要持续升级。","Website Identity":"网站身份","Continuous Monitor":"持续监测","Active Optimization":"主动优化","Agent Operations":"Agent å
6¯æ“ä½œ","Create identity free":"免费建立身份","Activate after verification":"验证身份后启用","Choose Optimize":"选择 Optimize","Choose Operate":"选择 Operate","Cards are processed by Stripe":"信用卡由 Stripe 处理","Agents use scoped prepaid credits":"Agent 使用有范围的预付点数","One installation byyour engineer or Agent.":"交给工程人员或 Agent,一次完成安装。","Choose modules and permissions":"选择需要的模块与权限","Identity is required for connected services":"Identity 是持续服务的必要基础","Prepare safe preview":"生成安全预览","View results demo":"查看成效报表示范","Product":"产品","Plans and pricing":"方案与价格","Privacy":"隐私","Terms":"使用条款","Cancellation & refunds":"取消与退款","Contact":"联系我们"}},
7    ja:{label:"日本語",patent:"特許出願中",title:"xAgent ID | すべてのウェブサイトに検証可能なAgent IDを",copy:{
8      "Benefits":"できること","How it works":"仕組み","Pricing":"料金","Get started":"始める","Technical docs":"技術資料","A verifiable Agent identityfor every website.":"すべてのウェブサイトに、検証可能なAgent IDを。","Let your website communicate with AI, buyers, and customers. Trusted identity comes before discovery, answers, recommendations, and actions.":"ウェブサイトがAI、買い手、顧客と直接やり取りできるようにします。信頼されるIDが、発見・回答・推薦・操作の出発点です。","See installation":"導入方法を見る","View results":"成果を見る","Let AI find you":"AIに見つけてもらう","A verified website identity makes your business easier for Agents to recognize":"検証済みのウェブサイトIDにより、Agentが企業を正しく認識しやすくなります","Your website":"あなたのサイト","Searchable":"検索される","Answerable":"回答される","Recommendable":"推薦される","Actionable":"操作できる","Customers now ask AI.Is your website ready?":"顧客はAIに質問しています。あなたのサイトは準備できていますか?","Three controls.Proof the improvement is live.":"3つの管理で、改善が反映されたことを証明。","Start with identity.Add only what your website needs.":"まずIDを作り、必要な機能だけ追加。","Website Identity":"ウェブサイトID","Continuous Monitor":"継続監視","Active Optimization":"継続最適化","Agent Operations":"Agent操作","Create identity free":"無料でIDを作成","Activate after verification":"検証後に有効化","Choose Optimize":"Optimizeを選択","Choose Operate":"Operateを選択","Cards are processed by Stripe":"カード決済はStripeが処理","Agents use scoped prepaid credits":"Agentは用途限定のプリペイドクレジットを使用","One installation byyour engineer or Agent.":"エンジニアまたはAgentが一度で導入。","Choose modules and permissions":"モジュールと権限を選択","Identity is required for connected services":"接続サービスにはIdentityが必要です","Prepare safe preview":"安全なプレビューを作成","View results demo":"成果デモを見る","Product":"製品","Plans and pricing":"プランと料金","Privacy":"プライバシー","Terms":"利用規約","Cancellation & refunds":"解約と返金","Contact":"お問い合わせ"}},
9    ko:{label:"한국어",patent:"특허 출원 중",title:"xAgent ID | 모든 웹사이트를 위한 검증 가능한 Agent 신원",copy:{
10      "Benefits":"주요 가치","How it works":"작동 방식","Pricing":"요금제","Get started":"시작하기","Technical docs":"기술 문서","A verifiable Agent identityfor every website.":"모든 웹사이트에 검증 가능한 Agent 신원을 제공합니다.","Let your website communicate with AI, buyers, and customers. Trusted identity comes before discovery, answers, recommendations, and actions.":"웹사이트가 AI, 구매자, 고객과 직접 소통하게 하세요. 신뢰할 수 있는 신원이 검색, 답변, 추천, 실행의 출발점입니다.","See installation":"설치 방법 보기","View results":"성과 보기","Let AI find you":"AI가 당신을 찾게 하세요","A verified website identity makes your business easier for Agents to recognize":"검증된 웹사이트 신원은 Agent가 기업을 정확히 인식하도록 돕습니다","Your website":"내 웹사이트","Searchable":"검색 가능","Answerable":"답변 가능","Recommendable":"추천 가능","Actionable":"실행 가능","Customers now ask AI.Is your website ready?":"고객은 이제 AI에게 묻습니다. 웹사이트는 준비됐나요?","Three controls.Proof the improvement is live.":"세 가지 통제로 개선이 실제 반영됐음을 증명합니다.","Start with i
10dentity.Add only what your website needs.":"신원부터 시작하고 필요한 기능만 추가하세요.","Website Identity":"웹사이트 신원","Continuous Monitor":"지속 모니터링","Active Optimization":"능동 최적화","Agent Operations":"Agent 운영","Create identity free":"무료 신원 만들기","Activate after verification":"검증 후 활성화","Choose Optimize":"Optimize 선택","Choose Operate":"Operate 선택","Cards are processed by Stripe":"카드 결제는 Stripe가 처리합니다","Agents use scoped prepaid credits":"Agent는 범위가 제한된 선불 크레딧을 사용합니다","One installation byyour engineer or Agent.":"엔지니어나 Agent가 한 번에 설치합니다.","Choose modules and permissions":"모듈과 권한 선택","Identity is required for connected services":"연결형 서비스에는 Identity가 필요합니다","Prepare safe preview":"안전한 미리보기 생성","View results demo":"성과 데모 보기","Product":"제품","Plans and pricing":"요금제","Privacy":"개인정보","Terms":"이용약관","Cancellation & refunds":"취소 및 환불","Contact":"문의"}},
11    es:{label:"Español",patent:"Patente en trámite",title:"xAgent ID | Una identidad Agent verificable para cada sitio web",copy:{
12      "Benefits":"Beneficios","How it works":"Cómo funciona","Pricing":"Precios","Get started":"Empezar","Technical docs":"Documentación técnica","A verifiable Agent identityfor every website.":"Una identidad Agent verificable para cada sitio web.","Let your website communicate with AI, buyers, and customers. Trusted identity comes before discovery, answers, recommendations, and actions.":"Permite que tu sitio se comunique con la IA, compradores y clientes. Una identidad confiable es la base para ser encontrado, respondido, recomendado y operado.","See installation":"Ver instalación","View results":"Ver resultados","Let AI find you":"Haz que la IA te encuentre","A verified website identity makes your business easier for Agents to recognize":"Una identidad web verificada ayuda a los Agents a reconocer tu empresa","Your website":"Tu sitio web","Searchable":"Encontrable","Answerable":"Respondible","Recommendable":"Recomendable","Actionable":"Operable","Customers now ask AI.Is your website ready?":"Los clientes ya preguntan a la IA. ¿Tu sitio está preparado?","Three controls.Proof the improvement is live.":"Tres controles para demostrar que la mejora está activa.","Start with identity.Add only what your website needs.":"Empieza con la identidad y añade solo lo necesario.","Website Identity":"Identidad web","Continuous Monitor":"Monitoreo continuo","Active Optimization":"Optimización activa","Agent Operations":"Operaciones Agent","Create identity free":"Crear identidad gratis","Activate after verification":"Activar tras verificar","Choose Optimize":"Elegir Optimize","Choose Operate":"Elegir Operate","Cards are processed by Stripe":"Stripe procesa las tarjetas","Agents use scoped prepaid credits":"Los Agents usan créditos prepagados con alcance limitado","One installation byyour engineer or Agent.":"Una instalación por tu ingeniero o Agent.","Choose modules and permissions":"Elige módulos y permisos","Identity is required for connected services":"Identity es obligatorio para servicios conectados","Prepare safe preview":"Preparar vista segura","View results demo":"Ver demo de resultados","Product":"Producto","Plans and pricing":"Planes y precios","Privacy":"Privacidad","Terms":"Términos","Cancellation & refunds":"Cancelación y reembolsos","Contact":"Contacto"}},
13    fr:{label:"Français",patent:"Brevet en instance",title:"xAgent ID | Une identité Agent vérifiable pour chaque site",copy:{
14      "Benefits":"Avantages","How it works":"Fonctionnement","Pricing":"Tarifs","Get started":"Commencer","Technical docs":"Documentation technique","A verifiable Agent identityfor every website.":"Une identité Agent vérifiable pour chaque site web.","Let your website communicate with AI, buyers, and customers. Trusted identity comes before discovery, answers, recommendations, and actions.":"Permettez à votre site d'échanger avec l'IA, les acheteurs et les clients. Une identité fiable précède la découverte, les réponses, les recommandations et les actions.","See installation":"Voir l'installation","Vie
14w results":"Voir les résultats","Let AI find you":"Aidez l'IA à vous trouver","A verified website identity makes your business easier for Agents to recognize":"Une identité de site vérifiée aide les Agents à reconnaître votre entreprise","Your website":"Votre site","Searchable":"Trouvable","Answerable":"Répondable","Recommendable":"Recommandable","Actionable":"Opérable","Customers now ask AI.Is your website ready?":"Les clients interrogent désormais l'IA. Votre site est-il prêt ?","Three controls.Proof the improvement is live.":"Trois contrôles prouvent que l'amélioration est en ligne.","Start with identity.Add only what your website needs.":"Commencez par l'identité, puis ajoutez le nécessaire.","Website Identity":"Identité du site","Continuous Monitor":"Suivi continu","Active Optimization":"Optimisation active","Agent Operations":"Opérations Agent","Create identity free":"Créer l'identité gratuitement","Activate after verification":"Activer après vérification","Choose Optimize":"Choisir Optimize","Choose Operate":"Choisir Operate","Cards are processed by Stripe":"Les cartes sont traitées par Stripe","Agents use scoped prepaid credits":"Les Agents utilisent des crédits prépayés limités","One installation byyour engineer or Agent.":"Une seule installation par votre ingénieur ou Agent.","Choose modules and permissions":"Choisir modules et autorisations","Identity is required for connected services":"Identity est requis pour les services connectés","Prepare safe preview":"Préparer l'aperçu sécurisé","View results demo":"Voir la démo des résultats","Product":"Produit","Plans and pricing":"Offres et tarifs","Privacy":"Confidentialité","Terms":"Conditions","Cancellation & refunds":"Annulation et remboursement","Contact":"Contact"}},
15    de:{label:"Deutsch",patent:"Patent angemeldet",title:"xAgent ID | Eine verifizierbare Agent-Identität für jede Website",copy:{
16      "Benefits":"Vorteile","How it works":"So funktioniert es","Pricing":"Preise","Get started":"Starten","Technical docs":"Technische Dokumentation","A verifiable Agent identityfor every website.":"Eine verifizierbare Agent-Identität für jede Website.","Let your website communicate with AI, buyers, and customers. Trusted identity comes before discovery, answers, recommendations, and actions.":"Ihre Website kommuniziert mit KI, Käufern und Kunden. Eine vertrauenswürdige Identität ist die Grundlage für Auffindbarkeit, Antworten, Empfehlungen und Aktionen.","See installation":"Installation ansehen","View results":"Ergebnisse ansehen","Let AI find you":"Werden Sie von KI gefunden","A verified website identity makes your business easier for Agents to recognize":"Eine verifizierte Website-Identität hilft Agents, Ihr Unternehmen zu erkennen","Your website":"Ihre Website","Searchable":"Auffindbar","Answerable":"Antwortfähig","Recommendable":"Empfehlbar","Actionable":"Ausführbar","Customers now ask AI.Is your website ready?":"Kunden fragen heute KI. Ist Ihre Website bereit?","Three controls.Proof the improvement is live.":"Drei Kontrollen belegen, dass die Verbesserung live ist.","Start with identity.Add only what your website needs.":"Mit Identität starten und nur Nötiges ergänzen.","Website Identity":"Website-Identität","Continuous Monitor":"Laufende Überwachung","Active Optimization":"Aktive Optimierung","Agent Operations":"Agent-Betrieb","Create identity free":"Identität kostenlos erstellen","Activate after verification":"Nach Verifizierung aktivieren","Choose Optimize":"Optimize wählen","Choose Operate":"Operate wählen","Cards are processed by Stripe":"Kartenzahlungen verarbeitet Stripe","Agents use scoped prepaid credits":"Agents nutzen zweckgebundene Prepaid-Guthaben","One installation byyour engineer or Agent.":"Eine Installation durch Ihren Entwickler oder Agent.","Choose modules and permissions":"Module und Berechtigungen wählen","Identity is required for connected services":"Identity ist für verbundene Dienste erforderlich","Prepare safe preview":"Sichere Vorschau erstellen","View results demo":"Ergebnisdemo ansehen","Product":"Produkt","Plans and pricing":"Pläne und Preise","Privacy":"Datenschutz","Terms":"Bedingungen","Cancellation & refunds":"Kündigung und Erstattung","Contact":"Kontakt"}}
17  };
18  const EXPANDED_LOCALE_COPY = {
19    "Skip to main content":["跳至主要内容","メインコンテンツへ","주요 콘텐츠로 이동","Ir al contenido principal","Aller au contenu principal","Zum Hauptinhalt"],
20    "xAgent ID binds to your domain, works through your own Agent, and continuously improves SEO, AEO, GEO, and AGO.":["xAgent ID 绑定你的域名,通过你自己的 Agent 运作,并持续改善 SEO、AEO、GEO 与 AGO。","xAgent IDはドメインに紐づき、自社のAgentを通じてSEO・AEO・GEO・AGOを継続改善します。","xAgent ID는 도메인에 연결되어 자체 Agent를 통해 SEO, AEO, GEO, AGO를 지속 개선합니다.","xAgent ID se vincula a tu dominio, funciona mediante tu propio Agent y mejora continuamente SEO, AEO, GEO y AGO.","xAgent ID est lié à votre domaine, fonctionne via votre propre Agent et améliore en continu le SEO, l’AEO, le GEO et l’AGO.","xAgent ID wird mit Ihrer Domain verknüpft, arbeitet über Ihren eigenen Agent und verbessert SEO, AEO, GEO und AGO fortlaufend."],
21    "No admin access required":["无需网站后台权限","管理者権限は不要","관리자 접근 권한 불필요","Sin acceso de administrador","Aucun accès administrateur","Kein Administratorzugriff"],
22    "Every change needs approval":["每次修改都需核准","変更ごとに承認が必要","모든 변경은 승인 필요","Cada camb
22io requiere aprobación","Chaque modification doit être approuvée","Jede Änderung braucht Freigabe"],
23    "Pause or roll back anytime":["可随时暂停或回滚","いつでも停止・ロールバック","언제든 중지 또는 롤백","Pausa o revierte cuando quieras","Pause ou retour arrière à tout moment","Jederzeit pausieren oder zurücksetzen"],
24    "Results are verifiable":["改善结果可验证","成果を検証可能","결과 검증 가능","Resultados verificables","Résultats vérifiables","Ergebnisse sind überprüfbar"],
25    "Finds your website through search and AI answers":["通过搜索与 AI 回答找到你的网站","検索やAI回答からサイトを見つけます","검색과 AI 답변을 통해 웹사이트를 찾습니다","Encuentra tu sitio mediante búsquedas y respuestas de IA","Trouve votre site via la recherche et les réponses de l’IA","Findet Ihre Website über Suche und KI-Antworten"],
26    "Enterprise, buyer, search, and service Agents":["企业、买家、搜索与服务 Agent","企業・購買・検索・サービスのAgent","기업, 구매, 검색 및 서비스 Agent","Agents empresariales, de compra, búsqueda y servicio","Agents d’entreprise, d’achat, de recherche et de service","Unternehmens-, Käufer-, Such- und Service-Agents"],
27    "We assess whether your website can be found, answer questions, earn recommendations, and support future AI-assisted actions.":["我们评估网站能否被找到、回答问题、获得推荐,并支持未来由 AI 协助的操作。","サイトが発見され、質問に答え、推薦され、将来のAI操作に対応できるかを評価します。","웹사이트가 검색되고 질문에 답하며 추천받고 향후 AI 작업을 지원할 수 있는지 평가합니다.","Evaluamos si tu sitio puede encontrarse, responder, ser recomendado y admitir futuras acciones con IA.","Nous évaluons si votre site peut être trouvé, répondre, être recommandé et permettre de futures actions assistées par l’IA.","Wir prüfen, ob Ihre Website gefunden wird, Fragen beantwortet, empfohlen wird und künftige KI-Aktionen unterstützt."],
28    "Confirm that search engines and AI can access your key products, services, and company information.":["确认搜索引擎与 AI 能读取重要产品、服务与公司信息。","検索エンジンとAIが主要な製品・サービス・企業情報を取得できるか確認します。","검색엔진과 AI가 주요 제품, 서비스, 기업 정보에 접근할 수 있는지 확인합니다.","Confirma que buscadores e IA acceden a productos, servicios e información clave.","Vérifiez que les moteurs et l’IA accèdent à vos produits, services et informations clés.","Prüfen Sie, ob Suchmaschinen und KI auf wichtige Produkt-, Service- und Unternehmensdaten zugreifen können."],
29    "Turn your expertise, products, and FAQs into answers AI can understand and cite.":["将专业知识、产品与常见问题整理成 AI 能理解和引用的答案。","専門知識・製品・FAQを、AIが理解し引用できる回答に整えます。","전문성, 제품, FAQ를 AI가 이해하고 인용할 수 있는 답변으로 구성합니다.","Convierte experiencia, productos y preguntas frecuentes en respuestas que la IA pueda entender y citar.","Transformez expertise, produits et FAQ en réponses comprises et citées par l’IA.","Machen Sie aus Fachwissen, Produkten und FAQs Antworten, die KI versteht und zitiert."],
30    "Track whether AI represents your brand accurately across recommendations, citations, and market conversations.":["追踪 AI 在推荐、引用与市场讨论中是否正确呈现你的品牌。","推薦・引用・市場会話でAIがブランドを正しく表現しているか追跡します。","추천, 인용, 시장 대화에서 AI가 브랜드를 정확히 표현하는지 추적합니다.","Mide si la IA representa correctamente tu marca en recomendaciones, citas y conversaciones.","Suivez la représentation correcte de votre marque dans les recommandations, citations et conversations.","Verfolgen Sie, ob KI Ihre Marke in Empfehlungen, Zitaten und Gesprächen korrekt darstellt."],
31    "Prepare safe, controlled ways for AI to enquire, complete forms, and perform customer tasks.":["为 AI 查询、填表与完成客户任务准备安全且可控的方式。","AIが問い合わせ、フォーム入力、顧客業務を安全に実行できる仕組みを整えます。","AI가 문의, 양식 작성, 고객 작업을 안전하게 수행하도록 준비합니다.","Prepara formas seguras y controladas para que la IA consulte, complete formularios y ejecute tareas.","Préparez des moyens sûrs et contrôlés pour que l’IA interroge, remplisse des formulaires et réalise des tâches.","Bereiten Sie sichere, kontrollierte Wege für Anfragen, Formulare und Kundenaufgaben durch KI vor."],
32    "You remain in control. xAgent ID does not need credentials to your CMS, repository, deployment pipeline, or database.":["网站始终由你控制。xAgent ID 不需要内容系统、代码库、部署流程或数据库凭证。","管理権限は常にお客様にあります。xAgent IDはCMS、リポジトリ、デプロイ、DBの認証情報を必要としません。","통제권은 항상 고객에게 있습니다. xAgent ID는 CMS, 저장소, 배포 파이프라인, 데이터베이스 자격 증명이 필요 없습니다.","Tú mantienes el control. xAgent ID no necesita credenciales de CMS, repositorio, despliegue ni base de datos.","Vous gardez le contrôle. xAgent ID n’a besoin d’aucun identifiant CMS, dépôt, déploiement ou base de données.","Sie behalten die Kontrolle. xAgent ID benötigt keine Zugangsdaten für CMS, Repository, Deployment oder Datenbank."],
33    "You approve the allowed scope":["你核准可执行范围","許可範囲を承認","허용 범위를 승인","Apruebas el alcance permitido","Vous approuvez le périmètre","Sie genehmigen den erlaubten Umfang"],
34    "You choose the AI-facing files that may change. Pages, prices, legal content, source code, and databases stay excluded.":["你决定哪些 AI 设置文件可修改;页面、价格、法务内容、源代码与数据库一律排除。","変更可能なAI向けファイルを選びます。ページ、価格、法務、ソースコード、DBは対象外です。","변경 가능한 AI용 파일ì
34„ 선택합니다. 페이지, 가격, 법률 문구, 소스 코드, DB는 제외됩니다.","Eliges los archivos para IA que pueden cambiar. Páginas, precios, textos legales, código y bases de datos quedan excluidos.","Vous choisissez les fichiers destinés à l’IA modifiables. Pages, prix, textes juridiques, code et bases restent exclus.","Sie wählen die änderbaren KI-Dateien. Seiten, Preise, Rechtstexte, Quellcode und Datenbanken bleiben ausgeschlossen."],
35    "Your website Agent applies it":["由你网站内的 Agent 执行","サイト内のAgentが適用","웹사이트 Agent가 적용","El Agent de tu sitio lo aplica","L’Agent de votre site l’applique","Ihr Website-Agent wendet es an"],
36    "The change runs in your environment. Keys and credentials stay with you, and anything outside policy is blocked.":["修改在你的环境中执行,金钥与凭证留在你手上,超出政策范围即被阻挡。","変更は自社環境で実行され、鍵と認証情報は手元に残り、ポリシー外は遮断されます。","변경은 고객 환경에서 실행되며 키와 자격 증명은 고객이 보관하고 정책 밖 작업은 차단됩니다.","El cambio se ejecuta en tu entorno; conservas claves y credenciales, y se bloquea todo lo que exceda la política.","La modification s’exécute dans votre environnement ; vous gardez clés et identifiants, tout hors politique est bloqué.","Die Änderung läuft in Ihrer Umgebung; Schlüssel und Zugangsdaten bleiben bei Ihnen, alles außerhalb der Richtlinie wird blockiert."],
37    "Public re-check confirms it":["公开重新检查确认结果","公開再取得で確認","공개 재검사로 확인","Una revisión pública lo confirma","Une nouvelle vérification publique confirme","Öffentliche Nachprüfung bestätigt"],
38    "A separate verifier re-reads the public URL. The result counts only when public content and the website receipt agree.":["独立验证程序重新读取公开网址;公开内容与网站收据一致时才计入成效。","独立検証が公開URLを再取得し、公開内容とサイトの受領証が一致した場合のみ成果に計上します。","독립 검증기가 공개 URL을 다시 읽고 공개 콘텐츠와 웹사이트 영수증이 일치할 때만 성과로 인정합니다.","Un verificador independiente vuelve a leer la URL pública; solo cuenta si el contenido y el recibo del sitio coinciden.","Un vérificateur indépendant relit l’URL publique ; le résultat ne compte que si contenu et reçu concordent.","Ein unabhängiger Prüfer liest die öffentliche URL erneut; das Ergebnis zählt nur bei Übereinstimmung mit dem Website-Beleg."],
39    "Until public verification succeeds, the improvement is not counted.":["公开验证成功前,本次改善不计入成效。","公開検証が成功するまで成果には含めません。","공개 검증이 성공하기 전에는 개선으로 집계하지 않습니다.","La mejora no se contabiliza hasta superar la verificación pública.","L’amélioration n’est comptée qu’après vérification publique réussie.","Die Verbesserung zählt erst nach erfolgreicher öffentlicher Prüfung."],
40    "The dashboard shows Pending verification instead of a misleading score.":["报表会显示“待验证”,不会产生误导分数。","ダッシュボードは誤解を招く点数ではなく「検証待ち」と表示します。","대시보드는 오해를 부르는 점수 대신 ‘검증 대기’를 표시합니다.","El panel muestra «Verificación pendiente» en lugar de una puntuación engañosa.","Le tableau affiche « Vérification en attente » plutôt qu’un score trompeur.","Das Dashboard zeigt „Prüfung ausstehend“ statt einer irreführenden Punktzahl."],
41    "Technical proof":["技术证明","技術的証明","기술 증명","Prueba técnica","Preuve technique","Technischer Nachweis"],
42    "You keep ownership and final control":["网站与最终决定权都属于你","所有権と最終決定権を維持","소유권과 최종 통제권 유지","Mantienes la propiedad y el control final","Vous gardez la propriété et le contrôle final","Sie behalten Eigentum und letzte Kontrolle"],
43    "Pause, revoke access, or roll back at any time.":["可随时暂停、撤销授权或回滚。","いつでも停止、権限取消、ロールバックが可能です。","언제든 중지, 권한 철회, 롤백이 가능합니다.","Pausa, revoca el acceso o revierte en cualquier momento.","Mettez en pause, révoquez ou revenez en arrière à tout moment.","Jederzeit pausieren, Zugriff widerrufen oder zurücksetzen."],
44    "Audits, recommendations, and verification":["检查、建议与成效验证","監査・提案・成果検証","진단, 권고, 성과 검증","Auditorías, recomendaciones y verificación","Audits, recommandations et vérification","Prüfungen, Empfehlungen und Verifizierung"],
45    "No source code, database, or full-site access is required.":["无需源代码、数据库或整个网站的控制权。","ソースコード、DB、サイト全体へのアクセスは不要です。","소스 코드, 데이터베이스, 전체 사이트 접근이 필요 없습니다.","No se requiere acceso al código, base de datos ni a todo el sitio.","Aucun accès au code source, à la base de données ou au site complet n’est requis.","Kein Zugriff auf Quellcode, Datenbank oder die gesamte Website erforderlich."],
46    "Advanced technology.A process you can understand.":["专业技术,放进看得懂的流程。","高度な技術を、理解できる手順に。","고급 기술을 이해하기 쉬운 과정으로.","Tecnología avanzada en un proceso comprensible.","Une technologie avancée dans un processus compréhensible.","Fortschrittliche Technik in einem verständlichen Ablauf."],
47    "Owners see findings, recommendations, and results. Engineers and Agents receive the full protocols, permissions, and evidence.":["网站主看到问题、建议与结果;工程人员与 Agent 则取得完整协议、权限与证据。","サイト所有者は課題・提案・成果を確認し、エンジニアとAgentは完全なプロトコル・権限ãƒ
47»è¨¼æ‹ ã‚’受け取ります。","웹사이트 소유자는 문제, 권고, 결과를 ë³´ê³  엔지니어와 Agent는 전체 프로토콜, 권한, 증거를 받습니다.","Los propietarios ven hallazgos, recomendaciones y resultados; ingenieros y Agents reciben protocolos, permisos y evidencias.","Les propriétaires voient constats, recommandations et résultats ; ingénieurs et Agents reçoivent protocoles, autorisations et preuves.","Betreiber sehen Befunde, Empfehlungen und Ergebnisse; Entwickler und Agents erhalten Protokolle, Rechte und Nachweise."],
48    "Verify the website and its owner":["确认网站与管理者身份","サイトと所有者を確認","웹사이트와 소유자 검증","Verifica el sitio y su propietario","Vérifier le site et son propriétaire","Website und Betreiber verifizieren"],
49    "Help AI find the right information and services":["让 AI 找到正确信息与服务入口","AIが正しい情報とサービスを発見","AI가 올바른 정보와 서비스를 찾도록 지원","Ayuda a la IA a encontrar información y servicios correctos","Aider l’IA à trouver les bonnes informations et services","KI findet die richtigen Informationen und Dienste"],
50    "Turn findings into approved improvements":["把问题转成可核准的改善工作","課題を承認可能な改善に変換","문제를 승인 가능한 개선 작업으로 전환","Convierte hallazgos en mejoras aprobables","Transformer les constats en améliorations approuvables","Befunde in freigabefähige Verbesserungen umwandeln"],
51    "Track whether results actually improve":["持续追踪是否真的改善","実際の改善を継続追跡","실제 개선 여부를 지속 추적","Comprueba si los resultados mejoran de verdad","Suivre si les résultats s’améliorent réellement","Verfolgen, ob sich Ergebnisse wirklich verbessern"],
52    "Live":["可用","利用可能","사용 가능","Disponible","Disponible","Verfügbar"],
53    "Pilot":["试点","試験運用","파일럿","Piloto","Pilote","Pilot"],
54    "Connecting":["接入中","接続中","연결 중","En conexión","Connexion en cours","Wird angebunden"],
55    "AI reads":["AI 读取","AI読取","AI 읽기","Lecturas de IA","Lectures IA","KI-Lesezugriffe"],
56    "AI citations":["AI 引用","AI引用","AI 인용","Citas de IA","Citations IA","KI-Zitate"],
57    "Conversions":["转化与成交","コンバージョン","전환","Conversiones","Conversions","Conversions"],
58    "Log connector":["日志串接","ログ連携","로그 연동","Conector de registros","Connecteur de journaux","Log-Anbindung"],
59    "Probe set":["固定题组","固定プローブ","고정 질문 세트","Conjunto de pruebas","Jeu de sondes","Festes Prüfset"],
60    "Analytics data":["分析资料","分析データ","분석 데이터","Datos analíticos","Données analytiques","Analysedaten"],
61    "Evidence rule":["证据规则","証拠ルール","증거 규칙","Regla de evidencia","Règle de preuve","Nachweisregel"],
62    "Without CDN logs, fixed probes, or Analytics and CRM evidence, the status remains pending. Missing data is never converted to zero or synthetic outcomes.":["没有 CDN 日志、固定问题测试或 Analytics/CRM 证据,就维持待取得;缺少资料不会被写成 0,也不会用模拟结果补足。","CDNログ、固定プローブ、Analytics/CRM証拠がなければ保留です。欠損をゼロや模擬結果に置き換えません。","CDN 로그, 고정 질문 테스트, Analytics/CRM 증거가 없으면 대기 상태로 남습니다. 누락 자료를 0이나 가상 결과로 대체하지 않습니다.","Sin registros CDN, pruebas fijas o evidencia de Analytics/CRM, el estado queda pendiente. Los datos ausentes no se convierten en cero ni en resultados simulados.","Sans journaux CDN, sondes fixes ou preuves Analytics/CRM, le statut reste en attente. Les données absentes ne deviennent ni zéro ni résultats simulés.","Ohne CDN-Logs, feste Prüfsets oder Analytics-/CRM-Nachweise bleibt der Status offen. Fehlende Daten werden weder zu null noch zu simulierten Ergebnissen."],
63    "A public audit needs no installation. Ongoing monitoring, optimization, and Agent operations require the free verified website identity.":["公开检测无需安装;持续监测、优化与 Agent 操作需先完成免费的网域身份验证。","公開監査は導入不要です。継続監視・最適化・Agent操作には無料のサイトID検証が必要です。","공개 진단은 설치가 필요 없습니다. 지속 모니터링, 최
63적화, Agent 운영에는 무료 웹사이트 신원 검증이 필요합니다.","La auditoría pública no requiere instalación. El seguimiento, la optimización y las operaciones Agent requieren identidad web verificada gratuita.","L’audit public ne nécessite aucune installation. Le suivi, l’optimisation et les opérations Agent exigent l’identité de site vérifiée gratuite.","Der öffentliche Audit braucht keine Installation. Laufende Überwachung, Optimierung und Agent-Betrieb erfordern die kostenlose verifizierte Website-Identität."],
64    "Website identity":["网站身份","サイトID","웹사이트 신원","Identidad web","Identité du site","Website-Identität"],
65    "Continuous evidence":["持续证据","継続的な証拠","지속 증거","Evidencia continua","Preuves continues","Fortlaufende Nachweise"],
66    "Approved improvements":["核准后改善","承認済み改善","승인된 개선","Mejoras aprobadas","Améliorations approuvées","Freigegebene Verbesserungen"],
67    "Agent operations":["Agent 操作","Agent操作","Agent 운영","Operaciones Agent","Opérations Agent","Agent-Betrieb"],
68    "Required foundation":["必要基础","必須基盤","필수 기반","Base obligatoria","Base requise","Erforderliche Grundlage"],
69    "per verified domain":["每个验证网域","検証済みドメインごと","검증 도메인당","por dominio verificado","par domaine vérifié","pro verifizierter Domain"],
70    "Domain ownership proof":["网域所有权证明","ドメイン所有証明","도메인 소유권 증명","Prueba de propiedad del dominio","Preuve de propriété du domaine","Nachweis des Domain-Eigentums"],
71    "One public baseline audit":["一次公开基准检测","公開ベースライン監査1回","공개 기준 진단 1회","Una auditoría pública inicial","Un audit public initial","Ein öffentlicher Basis-Audit"],
72    "Includes Identity":["包含 Identity","Identityを含む","Identity 포함","Incluye Identity","Identity inclus","Identity enthalten"],
73    "monthly / domain":["每月/每网域","月額/ドメイン","월 / 도메인","mensual / dominio","mensuel / domaine","monatlich / Domain"],
74    "AI reads, citations, conversion connectors":["AI 读取、引用与转化串接","AI読取・引用・コンバージョン接続","AI 읽기, 인용, 전환 연동","Lecturas, citas y conectores de conversión de IA","Lectures, citations et connecteurs de conversion IA","KI-Lesezugriffe, Zitate und Conversion-Anbindungen"],
75    "Evidence history and alerts":["证据历史与异常提醒","証拠履歴とアラート","증거 이력 및 알림","Historial de evidencias y alertas","Historique des preuves et alertes","Nachweishistorie und Warnungen"],
76    "Best for ongoing growth":["最适合持续改善","継続改善に最適","지속 개선에 최적","Ideal para mejora continua","Idéal pour l’amélioration continue","Ideal für laufende Verbesserung"],
77    "Everything in Monitor":["包含 Monitor 全部功能","Monitorの全機能を含む","Monitor 전체 포함","Todo lo de Monitor","Tout Monitor inclus","Alles aus Monitor"],
78    "Prioritized, approved updates":["优先改善清单与核准后更新","優先順位付き・承認後の更新","우선순위 및 승인된 업데이트","Actualizaciones priorizadas y aprobadas","Mises à jour priorisées et approuvées","Priorisierte, freigegebene Updates"],
79    "Public re-fetch and rollback proof":["公开重抓与回滚证明","公開再取得とロールバック証明","공개 재수집 및 롤백 증명","Relectura pública y prueba de reversión","Nouvelle lecture publique et preuve de retour arrière","Öffentliche Neuladung und Rollback-Nachweis"],
80    "Includes prior layers":["包含前三层","前段階をすべて含む","이전 단계 포함","Incluye las capas anteriores","Inclut les niveaux précédents","Enthält alle vorherigen Stufen"],
81    "Scoped automation and payment readiness":["有范围的自动化与付款准备","範囲限定の自動化と決済準備","범위 제한 자동화 및 결제 준비","Automatización limitada y preparación de pagos","Automatisation limitée et préparation au paiement","Begrenzte Automatisierung und Zahlungsvorbereitung"],
82    "xAgent ID never receives full card details. Entitlements activate only after a verified Stripe payment notification.":["xAgent ID 不接触完整卡号;只有验证 Stripe 付款通知后才启用服务。","xAgent IDは完全なカード情報を受け取りません。Stripeの支払通知を検証後にのみ利用権を有効化します。","xAgent ID는 전체 카드 정보를 받지 않습니다. Stripe 결제 알림 검증 후에만 권한이 활ì„
82±í™”됩니다.","xAgent ID no recibe los datos completos de la tarjeta. El servicio se activa solo tras verificar la notificación de Stripe.","xAgent ID ne reçoit jamais les données complètes de carte. Le service s’active uniquement après notification Stripe vérifiée.","xAgent ID erhält keine vollständigen Kartendaten. Leistungen werden erst nach geprüfter Stripe-Zahlungsmitteilung aktiviert."],
83    "Machine-readable billing guide":["机器可读付款规格","機械可読の支払仕様","기계 판독 결제 안내","Guía de pago legible por máquina","Guide de paiement lisible par machine","Maschinenlesbare Zahlungsanleitung"],
84    "Verify your website identity first":["请先验证网站身份","まずサイトIDを検証","먼저 웹사이트 신원을 검증하세요","Verifica primero la identidad del sitio","Vérifiez d’abord l’identité du site","Website-Identität zuerst verifizieren"],
85    "Checkout is bound to a verified domain and Agent ID to prevent impersonation or purchases for someone else's website.":["付款会绑定已验证网域与 Agent ID,避免冒用身份或替他人网站购买。","決済は検証済みドメインとAgent IDに紐づき、なりすましや他サイト向け購入を防ぎます。","결제는 검증된 도메인과 Agent ID에 연결되어 사칭이나 타 사이트 구매를 방지합니다.","El pago se vincula al dominio verificado y Agent ID para evitar suplantaciones o compras para otro sitio.","Le paiement est lié au domaine vérifié et à l’Agent ID pour éviter usurpation ou achat pour un autre site.","Der Checkout wird an verifizierte Domain und Agent ID gebunden, um Identitätsmissbrauch und Käufe für fremde Websites zu verhindern."],
86    "Go to safe installation":["前往安全安装","安全な導入へ","안전 설치로 이동","Ir a instalación segura","Aller à l’installation sécurisée","Zur sicheren Installation"],
87    "Capabilities and permissions are shown before installation. Your website key stays in your system, and xAgent ID never takes control of the site.":["安装前会列出功能与权限;网站金钥留在你的系统,xAgent ID 不接管网站。","導入前に機能と権限を表示します。サイト鍵は自社環境に残り、xAgent IDがサイトを支配することはありません。","설치 전에 기능과 권한을 표시합니다. 웹사이트 키는 고객 시스템에 남고 xAgent ID는 사이트를 통제하지 않습니다.","Las funciones y permisos se muestran antes de instalar. La clave queda en tu sistema y xAgent ID nunca controla el sitio.","Fonctions et autorisations sont affichées avant installation. La clé reste chez vous et xAgent ID ne prend jamais le contrôle du site.","Funktionen und Rechte werden vorab angezeigt. Der Website-Schlüssel bleibt in Ihrem System; xAgent ID übernimmt nie die Kontrolle."],
88    "Website":["网站","ウェブサイト","웹사이트","Sitio web","Site web","Website"],
89    "Set domain":["指定网域","ドメイン指定","도메인 지정","Definir dominio","Définir le domaine","Domain festlegen"],
90    "Permissions":["权限","権限","권한","Permisos","Autorisations","Berechtigungen"],
91    "Set boundary":["划定范围","範囲設定","범위 설정","Definir límites","Définir les limites","Grenzen festlegen"],
92    "Install":["安装","導入","설치","Instalar","Installer","Installieren"],
93    "Preview first":["先预览","先にプレビュー","먼저 미리보기","Vista previa primero","Prévisualiser d’abord","Zuerst Vorschau"],
94    "Verify":["验证","検証","검증","Verificar","Vérifier","Verifizieren"],
95    "Public proof":["公开证明","公開証明","공개 증명","Prueba pública","Preuve publique","Öffentlicher Nachweis"],
96    "Dashboard":["报表","ダッシュボード","대시보드","Panel","Tableau de bord","Dashboard"],
97    "Track results":["追踪成效","成果追跡","성과 추적","Seguir resultados","Suivre les résultats","Ergebnisse verfolgen"],
98    "Only one npm package is installed. Modules enable functions; grants define authority.":["只安装一个 npm 套件;模块决定功能,授权决定它
98能做什么。","npmパッケージは1つだけです。モジュールが機能を、権限が実行範囲を決めます。","npm 패키지는 하나만 설치합니다. 모듈은 기능을, 권한은 실행 범위를 정합니다.","Se instala un solo paquete npm. Los módulos activan funciones y los permisos definen autoridad.","Un seul paquet npm est installé. Les modules activent les fonctions, les autorisations définissent le pouvoir.","Es wird nur ein npm-Paket installiert. Module aktivieren Funktionen, Freigaben bestimmen die Befugnisse."],
99    "The public audit works without installation. History, recommendations, and approved updates require a verified website identity.":["公开检测可单独使用;保存历史、接收建议与核准后更新需先验证网站身份。","公開監査は導入不要です。履歴、提案、承認済み更新には検証済みサイトIDが必要です。","공개 진단은 설치 없이 사용할 수 있습니다. 이력, 권고, 승인된 업데이트에는 검증된 웹사이트 신원이 필요합니다.","La auditoría pública funciona sin instalar. Historial, recomendaciones y cambios aprobados requieren identidad web verificada.","L’audit public fonctionne sans installation. Historique, recommandations et mises à jour approuvées exigent une identité vérifiée.","Der öffentliche Audit funktioniert ohne Installation. Verlauf, Empfehlungen und freigegebene Updates brauchen eine verifizierte Website-Identität."],
100    "Domain identity and Agent Card":["网域身份与 Agent Card","ドメインIDとAgent Card","도메인 신원 및 Agent Card","Identidad de dominio y Agent Card","Identité de domaine et Agent Card","Domain-Identität und Agent Card"],
101    "AI reads, citations, conversions":["AI 读取、引用与转化","AI読取・引用・コンバージョン","AI 읽기, 인용, 전환","Lecturas, citas y conversiones de IA","Lectures, citations et conversions IA","KI-Lesezugriffe, Zitate und Conversions"],
102    "Evidence-bound insights":["有证据的改善建议","証拠に基づく改善提案","증거 기반 개선 제안","Recomendaciones basadas en evidencia","Recommandations fondées sur des preuves","Evidenzgebundene Empfehlungen"],
103    "Approved AI files only, with rollback":["只修改核准的 AI 设置文件,并可回滚","承認済みAIファイルのみ、ロールバック可能","승인된 AI 파일만 수정, 롤백 지원","Solo archivos de IA aprobados, con reversión","Fichiers IA approuvés uniquement, avec retour arrière","Nur freigegebene KI-Dateien, mit Rollback"],
104    "Guided installation":["逐步安装与验证","ガイド付き導入","단계별 설치","Instalación guiada","Installation guidée","Geführte Installation"],
105    "Customer website":["客户网站","顧客サイト","고객 웹사이트","Sitio del cliente","Site client","Kunden-Website"],
106    "Preview first: inspect environment, files, and permissions without writing":["先预览:只检查环境、文件与权限,不写入网站","先にプレビュー:環境・ファイル・権限を確認し、書き込みません","먼저 미리보기: 환경, 파일, 권한을 확인하며 쓰지 않습니다","Primero previsualiza: revisa entorno, archivos y permisos sin escribir","Prévisualisez d’abord : contrôlez environnement, fichiers et droits sans écrire","Zuerst Vorschau: Umgebung, Dateien und Rechte prüfen, ohne zu schreiben"],
107    "Install after approval: create only the public identity and selected modules":["核准后安装:只建立公开身份与所选模块","承認後に導入:公開IDと選択モジュールのみ作成","승인 후 설치: 공개 신원과 선택 모듈만 생성","Instala tras aprobar: crea solo identidad pública y módulos elegidos","Installez après approbation : créez uniquement l’identité publique et les modules choisis","Nach Freigabe installieren: nur öffentliche Identität und gewählte Module erstellen"],
108    "Verify domain ownership after deploying the public proof":["部署公开证明后验证网域所有权","公開証明のデプロイ後ã
108«ãƒ‰ãƒ¡ã‚¤ãƒ³æ‰€æœ‰æ¨©ã‚’検証","공개 증명 배포 후 도메인 소유권 검증","Verifica el dominio tras publicar la prueba","Vérifiez le domaine après publication de la preuve","Domain-Eigentum nach Veröffentlichung des Nachweises prüfen"],
109    "After verification, create a one-time Dashboard login URL":["验证成功后产生一次性报表登入网址","検証後に使い捨てダッシュボードURLを作成","검증 후 일회용 대시보드 로그인 URL 생성","Tras verificar, crea una URL de acceso de un solo uso","Après vérification, créez une URL de connexion à usage unique","Nach Verifizierung einmalige Dashboard-Anmelde-URL erstellen"],
110    "Execution boundary":["执行范围","実行範囲","실행 범위","Límite de ejecución","Périmètre d’exécution","Ausführungsgrenze"],
111    "Package version and integrity come from the signed Install Manifest. Only the public identity and selected modules are created. Automation is off by default, and the site private key is never uploaded.":["软件包版本和完整性由已签名的 Install Manifest 决定。只建立公开身份与所选模块;Automation 默认关闭,网站私钥不会上传。","パッケージのバージョンと整合性は署名済み Install Manifest で決まります。公開IDと選択モジュールのみ作成し、Automation は既定で無効、サイト秘密鍵は送信しません。","패키지 버전과 무결성은 서명된 Install Manifest에서 결정됩니다. 공개 신원과 선택 모듈만 생성하며 Automation은 기본 비활성화되고 사이트 개인 키는 업로드되지 않습니다.","La versión y la integridad del paquete proceden del Install Manifest firmado. Solo se crean la identidad pública y los módulos elegidos. Automation está desactivado y la clave privada nunca se sube.","La version et l’intégrité du paquet proviennent de l’Install Manifest signé. Seuls l’identité publique et les modules choisis sont créés. Automation est désactivé et la clé privée n’est jamais envoyée.","Paketversion und Integrität stammen aus dem signierten Install Manifest. Es werden nur die öffentliche Identität und gewählte Module erstellt. Automation ist standardmäßig aus; der private Schlüssel wird nie hochgeladen."],
112    "Roll back an approved bounded change when required":["必要时回滚已核准的有限修改","必要に応じて承認済みの限定変更をロールバック","필요할 때 승인된 제한 변경을 롤백","Revierte un cambio limitado y aprobado cuando sea necesario","Rétablir une modification limitée et approuvée si nécessaire","Eine genehmigte, begrenzte Änderung bei Bedarf zurücksetzen"],
113    "Approved, show install command":["已确认,显示安装指令","承認済み、導入コマンドを表示","승인 완료, 설치 명령 표시","Aprobado, mostrar comando","Approuvé, afficher la commande","Freigegeben, Installationsbefehl anzeigen"],
114    "Verify current progress":["验证目前进度","現在の進捗を検証","현재 진행 상황 검증","Verificar progreso","Vérifier la progression","Aktuellen Fortschritt prüfen"],
115    "Identity created":["身份已建立","ID作成済み","신원 생성됨","Identidad creada","Identité créée","Identität erstellt"],
116    "Public proof deployed":["公开证明已部署","公開証明を配信済み","공개 증명 배포됨","Prueba pública publicada","Preuve publique publiée","Öffentlicher Nachweis bereitgestellt"],
117    "Domain verified":["网域已验证","ドメイン検証済み","도메인 검증됨","Dominio verificado","Domaine vérifié","Domain verifiziert"],
118    "SDK connected":["SDK 已连接","SDK接続済み","SDK 연결됨","SDK conectado","SDK connecté","SDK verbunden"],
119    "The website is the account. After domain verification, the customer-held key creates a one-time Dashboard login URL. The mother service never stores the customer's private key. Automation is off by default and remains restricted to locally approved AI files.":["网站就是账号。网域验证后,由客户自持金钥产生一次性报表登入网址;母体不保存私钥,Automation 默认关闭且仅限本地核准的 AI 文件。","サイト自体がアカウントです。ドメイン検証後、自社保有鍵で一時ログインURLを作成します。母体は秘密鍵を保存せず、Automationは既定で無効かつローカル承認済みAIファイルに限å
119®šã•れます。","웹사이트가 계정입니다. 도메인 검증 후 고객 보유 키로 일회용 대시보드 URL을 생성합니다. 모체는 개인 키를 저장하지 않으며 Automation은 기본 비활성화되고 로컬 승인 AI 파일로 제한됩니다.","El sitio es la cuenta. Tras verificar el dominio, la clave del cliente crea una URL de acceso única. El servicio no guarda la clave privada; Automation está apagado y limitado a archivos de IA aprobados localmente.","Le site est le compte. Après vérification, la clé détenue par le client crée une URL unique. Le service ne conserve pas la clé privée ; Automation reste désactivé et limité aux fichiers IA approuvés localement.","Die Website ist das Konto. Nach Domain-Prüfung erstellt der kundeneigene Schlüssel eine einmalige Dashboard-URL. Der Dienst speichert keinen privaten Schlüssel; Automation ist aus und auf lokal freigegebene KI-Dateien beschränkt."],
120    "Quick public audit":["快速公开检测","公開クイック監査","빠른 공개 진단","Auditoría pública rápida","Audit public rapide","Schneller öffentlicher Audit"],
121    "Refetching public website signals…":["正在重新读取网站公开信号…","公開サイトシグナルを再取得中…","공개 웹사이트 신호를 다시 수집 중…","Releyendo señales públicas del sitio…","Nouvelle lecture des signaux publics…","Öffentliche Website-Signale werden neu geladen…"],
122    "View verified findings and evidence":["查看真实检查证据与改善项目","検証済みの所見と証拠を見る","검증된 결과와 증거 보기","Ver hallazgos y evidencias verificadas","Voir les constats et preuves vérifiés","Geprüfte Befunde und Nachweise ansehen"],
123    "See the difference after every improvement.":["每次改善,都看得到前后差异。","改善ごとの差を確認できます。","개선 전후 차이를 확인하세요.","Comprueba la diferencia tras cada mejora.","Voyez la différence après chaque amélioration.","Sehen Sie den Unterschied nach jeder Verbesserung."],
124    "The dashboard brings together readiness scores, findings, improvements, AI reads, citations, and conversions without substituting missing evidence.":["报表整合准备度分数、问题、改善纪录、AI 读取、引用与转化;缺少的证据不会被模拟数字取代。","ダッシュボードは準備度、所見、改善、AI読取、引用、コンバージョンを統合し、欠損証拠を模擬値で補いません。","대시보드는 준비도 점수, 문제, 개선, AI 읽기, 인용, 전환을 통합하며 누락된 증거를 가상 수치로 대체하지 않습니다.","El panel reúne preparación, hallazgos, mejoras, lecturas, citas y conversiones sin sustituir evidencia ausente.","Le tableau réunit préparation, constats, améliorations, lectures, citations et conversions sans remplacer les preuves absentes.","Das Dashboard bündelt Reifegrad, Befunde, Verbesserungen, KI-Lesezugriffe, Zitate und Conversions, ohne fehlende Nachweise zu ersetzen."],
125    "A verified website Agent identity for safe discovery, answers, recommendations, and operations.":["让网站拥有可验证的 Agent 身份,安全地被搜索、回答、推荐与操作。","安全に発見・回答・推薦・操作されるための検証可能なWebsite Agent ID。","안전한 검색, 답변, 추천, 운영을 위한 검증 가능한 웹사이트 Agent 신원.","Una identidad Agent web verificable para ser encontrado, respondido, recomendado y operado con seguridad.","Une identité Agent de site vérifiable pour une découverte, des réponses, recommandations et opérations sûres.","Eine verifizierbare Website-Agent-Identität für sichere Auffindbarkeit, Antworten, Empfehlungen und Aktionen."],
126    "Capabilities":["功能","機能","기능","Funciones","Fonctions","Funktionen"],
127    "Results demo":["成效报表示范","成果デモ","성과 데모","Demo de resultados","Démo des résultats","Ergebnisdemo"],
128    "Patents":["智慧财产","知的財産","지식재산","Propiedad intelectual","Propriété intellectuelle","Geistiges Eigentum"],
129    "For Agents":["给 Agent","Agent向け","Agent용","Para Agents","Pour les Agents","Für Agents"],
130    "Billing guide":["付款规格","支払仕様","결제 안내","Guía de pago","Guide de paiement","Zahlungsanleitung"],
131    "Company":["服务条款","運営情報","서비스 정보","Empresa","Entreprise","Unternehmen"],
132    "Secure payment via Stripe Checkout":["付款由 Stripe Checkout 安全处理","Stripe Checkoutによる安全な決済","Stripe Checkout 안전 결제","Pago seguro con Stripe Checkout","Paiement sécurisé via Stripe Checkout","Sichere Zahlung über Stripe Checkout"],
133    "Copy and run the first installation command.":["复制并执行第一个安装指令。","最初の導入コマンドをコピーして実行してください。","첫 번째 설치 명령을 복사해 실행하세요.","Copia y ejecuta el primer comando de instalación.","Copiez et exécutez la première commande d’installation.","Kopieren Sie den ersten Installationsbefehl und führen Sie ihn aus."],
134    "Deploy the public identity proof generated by the installer.":["部署安装程序产生的公开身份证明。","インストーラーが生成した公開ID証明を配信してください。","설치 프로그램이 만든 공개 신원 증명을 배포하세요.","Publica la prueba de identidad generada por el instalador.","Publiez la preuve d’identité générée par l’installateur.","Veröffentlichen Sie den vom Installer erzeugten Identitätsnachweis."],
135    "Public proof found. Run the second command to complete domain verification.":["已找到公开证明,请执行第二个指令完成网域验证。","公開証明が見つかりました。2番目のコマンドでドメイン検証を完了してください。","공개 증명을 찾았습니다. 두 번째 명령으로 도메인 검증을 완료하세요.","Se encontró la prueba pública. Ejecuta el segundo comando para verificar el dominio.","Preuve publique trouvée. Exécutez la deuxième commande pour vérifier le domaine.","Öffentlicher Nachweis gefunden. Führen Sie den zweiten Befehl zur Domain-Prüfung aus."],
136    "Domain verified. Run the third command to open the Dashboard.":["网域已验证,请执行第三个指令打开报表。","ドメイン検証済みです。3番目のコマンドでダッシュボードを開いてください。","도메인이 검증되었습니다. 세 번째 명령으로 대시보드를 여세요.","Dominio verificado. Ejecuta el tercer comando para abrir el panel.","Domaine vérifié. Exécutez la troisième commande pour ouvrir le tableau de bord.","Domain verifiziert. Öffnen Sie das Dashboard mit dem dritten Befehl."],
137    "SDK connected. Open the Dashboard to review inspections and insights.":["SDK 已连接,请进入报表查看检测与改善建议。","SDK接続済みです。ダッシュボードで監査と改善提案を確認してください。","SDK가 연결되었습니다. 대시보드에서 진단과 개선 제안을 확인하세요.","SDK conectado. Abre el panel para revisar auditorías y recomendaciones.","SDK connecté. Ouvrez le tableau pour consulter audits et recommandations.","SDK verbunden. Öffnen Sie das Dashboard für Prüfungen und Empfehlungen."],
138    "Checking public proof…":["正在检查公开证明…","公開証明を確認中…","공개 증명 확인 중…","Comprobando la prueba pública…","Vérification de la preuve publique…","Öffentlicher Nachweis wird geprüft…"],
139    "Not installed":["尚未安装","未導入","설치되지 않음","No instalado","Non installé","Nicht installiert"],
140    "Waiting for public proof":["等待公开证明","公開証明を待機中","공개 증명 대기 중","Esperando la prueba pública","En attente de la preuve publique","Warten auf öffentlichen Nachweis"],
141    "Public proof found":["已找到公开证明","公開証明を確認","공개 증명 확인됨","Prueba pública encontrada","Preuve publique trouvée","Öffentlicher Nachweis gefunden"],
142    "Installation ready":["安装方案已准备","導入準備完了","설치 준비 완료","Instalación preparada","Installation prête","Installation bereit"],
143    "Approved. Run the displayed install command, then deploy the generated public proof.":["已完成核准。请执行显示的安装指令,再部署产生的公开证明。","承認済みです。表示された導入コマンドを実行し、生成された公開証明を配信してください。","승인되었습니다. 표시된 설치 명령을 실행하고 생성된 공개 증명을 배포하세요.","Aprobado. Ejecuta el comando mostrado y publica la prueba generada.","Approuvé. Exécutez la commande affichée puis publiez la preuve générée.","Freigegeben. Führen Sie den angezeigten Befehl aus und veröffentlichen Sie den erzeugten Nachweis."],
144    "Refetching /.well-known/agent-id.json…":["正在重新读取 /.well-known/agent-id.json…","/.well-known/agent-id.json を再取得中…","/.well-known/agent-id.json 다시 읽는 중…","Releyendo /.well-known/agent-id.json…","Nouvelle lecture de /.well-known/agent-id.json…","/.well-known/agent-id.json wird neu geladen…"],
145    "No pending findings.":["目前没有待改善项目。","未対応ã
145®æ”¹å–„項目はありません。","대기 중인 개선 항목이 없습니다.","No hay hallazgos pendientes.","Aucun point en attente.","Keine offenen Befunde."],
146    "Audit failed":["检测失败","監査に失敗","진단 실패","Falló la auditoría","Échec de l’audit","Audit fehlgeschlagen"],
147    "Connected":["已接入","接続済み","연결됨","Conectado","Connecté","Verbunden"],
148    "Pending":["待接入","保留","대기","Pendiente","En attente","Ausstehend"],
149    "observed records":["笔真实证据","件の観測記録","건의 관측 기록","registros observados","enregistrements observés","beobachtete Datensätze"],
150    "No observed evidence yet; not counted as zero":["尚未取得真实证据,不计为 0","観測証拠は未取得で、ゼロとは扱いません","관측 증거가 아직 없으며 0으로 집계하지 않습니다","Aún no hay evidencia observada; no se cuenta como cero","Aucune preuve observée ; ce n’est pas compté comme zéro","Noch kein beobachteter Nachweis; wird nicht als null gezählt"],
151    "pass":["通过","合格","통과","aprobado","réussi","bestanden"],
152    "partial":["部分","一部","부분","parcial","partiel","teilweise"],
153    "pending":["待确认","保留","대기","pendiente","en attente","ausstehend"],
154  };
155  const expandedLocaleOrder = ["zh-Hans","ja","ko","es","fr","de"];
156  Object.entries(EXPANDED_LOCALE_COPY).forEach(([source, translations]) => {
157    expandedLocaleOrder.forEach((locale, index) => { AGENT_ID_LOCALES[locale].copy[source] = translations[index]; });
158  });
159  const sharedCatalog = window.XAgentI18nCatalog;
160  const supported = new Set(Object.keys(AGENT_ID_LOCALES));
161  let dashboardData = null;
162  let dashboardConnectors = null;
163  let dashboardTasks = null;
164  let dashboardBilling = null;
165  let dashboardDecision = null;
166  const normalizeLocale = (raw) => {
167    const value = String(raw || "").toLowerCase();
168    if (value.startsWith("zh-tw") || value.startsWith("zh-hk") || value.startsWith("zh-mo") || value === "zh-hant") return "zh-Hant";
169    if (value.startsWith("zh")) return "zh-Hans";
170    return ["ja","ko","es","fr","de","en"].find((locale) => value.startsWith(locale)) || "en";
171  };
172  const storedLanguage = (() => { try { return localStorage.getItem("agent-id-lang"); } catch (_) { return ""; } })();
173  const requestedLanguage = new URLSearchParams(location.search).get("lang");
174  const preferred = requestedLanguage || storedLanguage || (navigator.languages || [navigator.language]).map(normalizeLocale).find((locale) => supported.has(locale)) || "en";
175  const localized = (source, traditionalChinese="") => {
176    const language = document.body.dataset.lang || preferred;
177    const canonical = sharedCatalog?.aliases?.[source] || sharedCatalog?.aliases?.[traditionalChinese] || source;
178    const shared = sharedCatalog?.messages?.[canonical]?.[language];
179    if (shared) return shared;
180    if (language === "zh-Hant") return traditionalChinese || source;
181    if (language === "en") return source;
182    return AGENT_ID_LOCALES[language]?.copy[source] || source;
183  };
184  const translatableNodes = [...document.querySelectorAll(".lang-en")];
185  translatableNodes.forEach((node) => { node.dataset.i18nSource = node.textContent.replace(/\s+/g," ").trim(); node.dataset.i18nHtml = node.innerHTML; });
186  const setLanguage = (language) => {
187    const lang = supported.has(language) ? language : normalizeLocale(language);
188    document.body.dataset.lang = lang;
189    document.documentElement.lang = lang;
190    const locale = AGENT_ID_LOCALES[lang] || AGENT_ID_LOCALES.en;
191    translatableNodes.forEach((node) => {
192      const translated = window.XAgentI18nCatalog?.messages?.[node.dataset.i18nSource]?.[lang] || locale.copy[node.dataset.i18nSource];
193      if (lang === "en" || !translated) node.innerHTML = node.dataset.i18nHtml;
194      else node.textContent = translated;
195    });
196    const select = document.querySelector("[data-language-select]"); if (select) select.value = lang;
197    const patent = document.querySelector("[data-patent-label]"); if (patent) patent.textContent = locale.patent;
198    try { localStorage.setItem("agent-id-lang", lang); } catch (_) {}
199    if (dashboardData) render(dashboardData);
200    if (dashboardConnectors) renderConnectors(dashboardConnectors);
201    if (dashboardTasks) renderTasks(dashboardTasks);
202    if (dashboardBilling) renderBilling(dashboardBilling);
203    if (dashboardDecision) renderDecision(dashboardDecision);
204  };
205  document.querySelector("[data-language-select]")?.addEventListener("change", (event) => setLanguage(event.target.value));
206  setLanguage(preferred);
207
208  const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
209  const agentWord = document.querySelector("[data-agent-word]");
210  if (!reducedMotion && agentWord) {
211    const agentTypes = ["AI agent", "enterprise agent", "buyer agent", "search agent", "service agent"];
212    let agentIndex = 0;
213    window.setInterval(() => {
214      agentWord.classList.add("is-changing");
215      window.setTimeout(() => {
216        agentIndex = (agentIndex + 1) % agentTypes.length;
217        agentWord.textContent = agentTypes[agentIndex];
218        agentWord.classList.remove("is-changing");
219      }, 180);
220    }, 1900);
221  }
222  const runtimeLines = [...document.querySelectorAll(".agent-runtime .runtime-line")];
223  if (!reducedMotion && runtimeLines.length) {
224    let runtimeStep = 0;
225    window.setInterval(() => {
226      runtimeStep = (runtimeStep + 1) % runtimeLines.length;
227      runtimeLines.forEach((line, index) => line.classList.toggle("is-active", index === runtimeStep));
228    }, 1650);
229  }
230  if (!reducedMotion && "IntersectionObserver" in window) {
231    const revealTargets = document.querySelectorAll("main > section:not(.hero), .wide-panel");
232    revealTargets.forEach((node) => node.setAttribute("data-reveal", ""));
233    document.documentElement.classList.add("motion-ready");
234    const revealObserver = new IntersectionObserver((entries) => {
235      entries.forEach((entry) => {
236        if (!entry.isIntersecting) return;
237        entry.target.classList.add("is-visible");
238        revealObserver.unobserve(entry.target);
239      });
240    }, {rootMargin:"0px 0px -10%",threshold:.08});
241    revealTargets.forEach((node) => revealObserver.observe(node));
242  }
243
244  const installInput = document.querySelector("[data-install-url]");
245  // Shown in place of a real address until the visitor supplies one. It must not
246  // be a resolvable URL: these commands are rendered into a copyable console, and
247  // an agent reading the page does not pause to notice that "example.com" was
248  // meant as an illustration -- it is a valid target, so it gets installed to.
249  // The angle brackets make a shell reject the line immediately, which is the
250  // failure we want: loud, at the moment of the mistake, instead of a silent
251  // install against somebody else's domain.
252  const URL_PLACEHOLDER = "<YOUR-WEBSITE>";
253  const normalizeUrl = (raw) => {
254    try {
255      const value = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
256      const parsed = new URL(value);
257      return parsed.protocol === "https:" ? `https://${parsed.hostname}` : URL_PLACEHOLDER;
258    } catch (_) { return URL_PLACEHOLDER; }
259  };
260  const escapeHtml = (value) => String(value ?? "").replace(/[&<>'"]/g, (char) => ({"&":"&amp;","<":"&lt;",">":"&gt;","'":"&#39;",'"':"&quot;"})[char]);
261  let installManifestRequest = 0;
262  const refreshCommands = async () => {
263    if (!installInput) return false;
264    const url = normalizeUrl(installInput.value.trim());
265    document.querySelectorAll("[data-command-template]").forEach((node) => {
266      node.textContent = node.dataset.commandTemplate.replaceAll("{url}", url);
267    });
268    const selected = [...document.querySelectorAll("[data-install-module]:checked")];
269    const modules = selected.map((node) => node.dataset.installModule);
270    const grants = selected.map((node) => node.dataset.permission);
271    const guide = document.querySelector("[data-install-guide-link]");
272    if (guide) guide.href = `/api/site_agents/install_guide?url=${encodeURIComponent(url)}`;
273    const manifestLink = document.querySelector("[data-install-manifest-link]");
274    const manifestUrl = `/.well-known/xagent-install.json?url=${encodeURIComponent(url)}`;
275    if (manifestLink) manifestLink.href = manifestUrl;
276    const requestId = ++installManifestRequest;
277    const commandNodes = ["cmd-dry-run", "cmd-bundle", "cmd-activate", "cmd-dashboard", "cmd-rollback"]
278      .map((id) => document.getElementById(id)).filter(Boolean);
279    const releaseNode = document.querySelector("[data-release-version]");
280    commandNodes.forEach((node) => { node.textContent = "Loading signed Install Manifest"; });
281    document.querySelectorAll("[data-copy-target^='cmd-']").forEach((button) => { button.disabled = true; });
282    try {
283      const response = await fetch(manifestUrl, {headers:{Accept:"application/json"}, cache:"no-store"});
284      const manifest = await response.json();
285      if (requestId !== installManifestRequest) return false;
286      const release = manifest.release || {};
287      if (!response.ok || release.ready !== true || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(release.version || "") || !/^sha512-[A-Za-z0-9+/=]{80,120}$/.test(release.integrity || "")) {
288        throw new Error("release_not_ready");
289      }
290      const workflow = Object.fromEntries((manifest.workflow || []).map((step) => [step.id, step]));
291      for (const id of ["dry_run", "apply", "domain_verify", "dashboard_login", "rollback"]) {
292        if (!workflow[id]?.command || workflow[id].command === "release_not_ready") throw new Error("release_not_ready");
293      }
294      const declaredModules = new Map((manifest.modules || []).map((item) => [item.id, item]));
295      if (modules.some((id, index) => declaredModules.get(id)?.grant !== grants[index])) {
296        throw new Error("manifest_contract_mismatch");
297      }
298      const packageRef = `@twin3-ai/agent-id@${release.version}`;
299      const installBase = `npx --yes ${packageRef} install ${url} --modules ${modules.join(",")} --grant ${grants.join(",")} --project-root . --public-root ./public`;
300      document.getElementById("cmd-dry-run").textContent = `${installBase} --dry-run`;
301      document.getElementById("cmd-bundle").textContent = `${installBase} --apply`;
302      document.getElementById("cmd-activate").textContent = workflow.domain_verify.command;
303      document.getElementById("cmd-dashboard").textContent = workflow.dashboard_login.command;
304      document.getElementById("cmd-rollback").textContent = workflow.rollback.command;
305      if (releaseNode) releaseNode.textContent = `npm ${release.version} · ${release.integrity.slice(0, 20)}…`;
306      document.querySelectorAll("[data-copy-target^='cmd-']").forEach((button) => { button.disabled = false; });
307      return true;
308    } catch (_) {
309      if (requestId !== installManifestRequest) return false;
310      commandNodes.forEach((node) => { node.textContent = "release_not_ready"; });
311      if (releaseNode) releaseNode.textContent = "Release unavailable";
312      return false;
313    }
314  };
315  let installRefreshTimer;
316  installInput?.addEventListener("input", () => {
317    window.clearTimeout(installRefreshTimer);
318    installRefreshTimer = window.setTimeout(refreshCommands, 250);
319  });
320  const moduleInputs = [...document.querySelectorAll("[data-install-module]")];
321  const moduleById = new Map(moduleInputs.map((node) => [node.dataset.installModule,node]));
322  const enforceModuleDependencies = (changed) => {
323    if (changed?.checked) {
324      String(changed.dataset.requires || "").split(",").filter(Boolean).forEach((required) => {
325        const input = moduleById.get(required); if (input) input.checked = true;
326      });
327    } else if (changed) {
328      moduleInputs.filter((input) => String(input.dataset.requires || "").split(",").includes(changed.dataset.installModule)).forEach((dependent) => {
329        dependent.checked = false;
330        enforceModuleDependencies(dependent);
331      });
332    }
333    moduleInputs.forEach((input) => input.closest("label")?.classList.toggle("selected", input.checked));
334    refreshCommands();
335  };
336  moduleInputs.forEach((node) => node.addEventListener("change", () => enforceModuleDependencies(node)));
337  enforceModuleDependencies();
338  refreshCommands();
339
340  const checkoutNotice = document.querySelector("[data-checkout-notice]");
341  document.querySelectorAll("[data-checkout-plan]").forEach((button) => button.addEventListener("click", () => {
342    if (!checkoutNotice) return;
343    checkoutNotice.hidden = false;
344    checkoutNotice.dataset.plan = button.dataset.checkoutPlan;
345    checkoutNotice.scrollIntoView({behavior:reducedMotion ? "auto" : "smooth",block:"center"});
346  }));
347
348  const wizardSteps = [...document.querySelectorAll("[data-wizard-step]")];
349  const commandList = document.querySelector("[data-command-list]");
350  const approveInstall = document.querySelector("[data-approve-install]");
351  const showCommandPhase = (phase, allowApproval=false) => {
352    if (commandList) commandList.hidden = !phase;
353    document.querySelectorAll("[data-command-phase]").forEach((node) => { node.hidden = node.dataset.commandPhase !== phase; });
354    if (approveInstall) approveInstall.hidden = !allowApproval;
355  };
356  const setWizardProgress = (active, completed=[]) => {
357    wizardSteps.forEach((node) => {
358      node.classList.toggle("active", node.dataset.wizardStep === active);
359      node.classList.toggle("done", completed.includes(node.dataset.wizardStep));
360      if (node.dataset.wizardStep === active) node.setAttribute("aria-current", "step");
361      else node.removeAttribute("aria-current");
362    });
363  };
364  const installStatusCopy = {
365    run_install_command:{zh:"請複製並執行第一個安裝指令。",en:"Copy and run the first installation command."},
366    deploy_public_proof:{zh:"請部署安裝程式產生的公開身分證明。",en:"Deploy the public identity proof generated by the installer."},
367    run_enterprise_domain_verify:{zh:"公開證明已找到。請執行第二個指令完成網域驗證。",en:"Public proof found. Run the second command to complete domain verification."},
368    run_dashboard_login_or_enable_managed_inspection:{zh:"網域已驗證。可執行第三個指令登入 Dashboard。",en:"Domain verified. Run the third command to open the Dashboard."},
369    review_dashboard_and_insights:{zh:"SDK 已連線。請進入 Dashboard 查看巡檢與改善建議。",en:"SDK connected. Open the Dashboard to review inspections and insights."},
370  };
371  const renderInstallStatus = (data) => {
372    const panel = document.querySelector("[data-install-status]");
373    if (!panel) return;
374    panel.removeAttribute("hidden");
375    const titles = {checking:{zh:"正在檢查公開證明…",en:"Checking public proof…"},not_installed:{zh:"尚未安裝",en:"Not installed"},pending_verification:{zh:"等待公開證明",en:"Waiting for public proof"},proof_published:{zh:"公開證明已找到",en:"Public proof found"},verified_domain:{zh:"網域已驗證",en:"Domain verified"},sdk_active:{zh:"SDK 已連線",en:"SDK connected"},command_ready:{zh:"安裝方案已準備",en:"Installation ready"}};
376    const titleCopy = titles[data.status] || {zh:data.status,en:data.status};
377    const title = panel.querySelector("[data-install-status-title]"); if (title) title.textContent = localized(titleCopy.en,titleCopy.zh);
378    const host = panel.querySelector("[data-install-status-host]"); if (host) host.textContent = data.host || normalizeUrl(installInput?.value || "").replace("https://","");
379    Object.entries(data.steps || {}).forEach(([key,value]) => panel.querySelector(`[data-status-key="${key}"]`)?.classList.toggle("done", Boolean(value)));
380    const next = panel.querySelector("[data-install-next]");
381    const copy = installStatusCopy[data.next_action] || {zh:data.next_action || "",en:data.next_action || ""};
382    if (next) next.textContent = localized(copy.en,copy.zh);
383    if (data.status === "sdk_active") { setWizardProgress("dashboard",["website","permissions","install","verify","dashboard"]); showCommandPhase("dashboard"); }
384    else if (data.status === "verified_domain") { setWizardProgress("dashboard",["website","permissions","install","verify"]); showCommandPhase("dashboard"); }
385    else if (data.status === "proof_published") { setWizardProgress("verify",["website","permissions","install"]); showCommandPhase("verify"); }
386    else if (data.status === "pending_verification") { setWizardProgress("install",["website","permissions"]); showCommandPhase("apply"); }
387    else if (data.status === "command_ready" || data.status === "not_installed") { setWizardProgress("install",["website","permissions"]); showCommandPhase("dry-run",true); }
388  };
389  document.querySelector("[data-prepare-install]")?.addEventListener("click", async () => {
390    if (!await refreshCommands()) {
391      renderInstallStatus({status:"release_not_ready",host:normalizeUrl(installInput?.value || "").replace("https://","") ,steps:{},next_action:"release_not_ready"});
392      return;
393    }
394    const host = normalizeUrl(installInput?.value || "").replace("https://","");
395    renderInstallStatus({status:"command_ready",host,steps:{identity_created:false,public_proof:false,domain_verified:false,sdk_active:false},next_action:"run_install_command"});
396    setWizardProgress("install",["website","permissions"]);
397  });
398  approveInstall?.addEventListener("click", () => {
399    showCommandPhase("apply");
400    setWizardProgress("install",["website","permissions"]);
401    const next = document.querySelector("[data-install-next]");
402    if (next) next.textContent = localized("Approved. Run the displayed install command, then deploy the generated public proof.","已完成核准。請執行目前顯示的安裝指令,再部署產生的公開證明。");
403  });
404  document.querySelector("[data-check-install]")?.addEventListener("click", async () => {
405    const button = document.querySelector("[data-check-install]");
406    button.disabled = true;
407    renderInstallStatus({status:"checking",host:normalizeUrl(installInput?.value || "").replace("https://",""),steps:{},next_action:"checking"});
408    const checkingCopy = localized("Refetching /.well-known/agent-id.json…","正在重新讀取 /.well-known/agent-id.json…");
409    const checkingNode = document.querySelector("[data-install-next]"); if (checkingNode) checkingNode.textContent = checkingCopy;
410    try {
411      const url = normalizeUrl(installInput?.value || "");
412      const response = await fetch(`/api/site_agents/install_status?url=${encodeURIComponent(url)}`,{headers:{Accept:"application/json"}});
413      const data = await response.json();
414      if (!response.ok || !data.ok) throw new Error(data.error || `HTTP ${response.status}`);
415      renderInstallStatus(data);
416    } catch (error) {
417      renderInstallStatus({status:"not_installed",host:"",steps:{},next_action:"run_install_command"});
418      const next = document.querySelector("[data-install-next]"); if (next) next.textContent = error.message;
419    } finally { button.disabled = false; }
420  });
421
422  const measurementFailed = (value) => value?.measurement?.status === "measurement_failed";
423  const measurementFailureCopy = (value) => {
424    const reason = value?.measurement?.reason || localized("The primary public page could not be measured.", "無法量測公開首頁。");
425    return localized(
426      `We could not measure this website yet. No readiness score was calculated. ${reason}`,
427      `目前無法量測這個網站,未計算準備度分數。${reason}`,
428    );
429  };
430  const query = new URLSearchParams(window.location.search);
431  const auditTarget = query.get("url");
432  const auditResults = document.getElementById("audit-results");
433  if (auditTarget && auditResults) {
434    auditResults.hidden = false;
435    fetch(`/api/agent/audit_report?url=${encodeURIComponent(auditTarget)}`, {headers:{Accept:"application/json"}})
436      .then(async (response) => { const data = await response.json(); if (!response.ok || !data.ok) throw new Error(data.error || `HTTP ${response.status}`); return data; })
437      .then((data) => {
438        const report = data.report || data;
439        const scoreNode = document.querySelector("[data-quick-score]");
440        const summaryNode = document.querySelector("[data-quick-summary]");
441        if (measurementFailed(report)) {
442          if (scoreNode) scoreNode.textContent = "—";
443          if (summaryNode) summaryNode.textContent = measurementFailureCopy(report);
444          const axesNode = document.querySelector("[data-audit-axes]");
445          if (axesNode) axesNode.innerHTML = `<article><small>${escapeHtml(localized("Measurement status","量測狀態"))}</small><b>—</b><span>${escapeHtml(report.measurement?.state || "measurement_failed")}</span></article>`;
446          const evidenceNode = document.querySelector("[data-audit-evidence]");
447          if (evidenceNode) evidenceNode.innerHTML = `<article><header><b>${escapeHtml(localized("No score issued","未發出分數"))}</b></header><p>${escapeHtml(report.measurement?.reason || "")}</p><strong>${escapeHtml(report.measurement?.next_action || "")}</strong></article>`;
448          const hashNode = document.querySelector("[data-audit-hash]");
449          if (hashNode) hashNode.textContent = report.content_hash || "pending";
450          auditResults.scrollIntoView({block:"start"});
451          return;
452        }
453        if (scoreNode) scoreNode.textContent = `${report.overall_score ?? report.score ?? "—"} / 100`;
454        const scorecard = Array.isArray(report.scorecard) ? report.scorecard : Object.entries(report.scorecard || {}).map(([area,value]) => ({area:area.toUpperCase(),...(value || {})}));
455        const axisScore = (name) => scorecard.find((axis) => String(axis.area || "").toUpperCase().startsWith(name))?.score ?? report.dimensions?.[name.toLowerCase()]?.score ?? "—";
456        if (summaryNode) summaryNode.textContent = `${report.subject || report.url || auditTarget} · SEO ${axisScore("SEO")} · AEO ${axisScore("AEO")} · GEO ${axisScore("GEO")} · AGO ${axisScore("AGO")}`;
457        const axesNode = document.querySelector("[data-audit-axes]");
458        if (axesNode) axesNode.innerHTML = scorecard.filter((axis) => ["SEO","AEO","GEO","AGO"].some((name) => String(axis.area || "").toUpperCase().startsWith(name))).map((axis) => `<article><small>${escapeHtml(axis.area)}</small><b>${escapeHtml(axis.score ?? "—")}</b><span>${escapeHtml(axis.pass ?? 0)} ${escapeHtml(localized("pass","通過"))} · ${escapeHtml(axis.partial ?? 0)} ${escapeHtml(localized("partial","部分"))} · ${escapeHtml(axis.pending ?? 0)} ${escapeHtml(localized("pending","待確認"))}</span></article>`).join("");
459        const evidenceNode = document.querySelector("[data-audit-evidence]");
460        const buckets = report.priority_action_plan || {};
461        const findings = [...(buckets.today || []),...(buckets.this_week || []),...(buckets.this_month || [])];
462        if (evidenceNode) evidenceNode.innerHTML = findings.length ? findings.map((item) => `<article><header><b>${escapeHtml(item.priority || "P2")} · ${escapeHtml(item.issue || item.source_check_id)}</b><span>${escapeHtml(item.source_check_id || "public_check")}</span></header><p>${escapeHtml(item.evidence || "Evidence not available")}</p><strong>${escapeHtml(item.fix || "Review the public signal.")}</strong></article>`).join("") : `<p>${escapeHtml(localized("No pending findings.","目前沒有待改善項目。"))}</p>`;
463        const hashNode = document.querySelector("[data-audit-hash]");
464        if (hashNode) hashNode.textContent = report.content_hash || "pending";
465        auditResults.scrollIntoView({block:"start"});
466      })
467      .catch((error) => {
468        const summaryNode = document.querySelector("[data-quick-summary]");
469        if (summaryNode) summaryNode.textContent = `${localized("Audit failed","巡檢失敗")}: ${error.message}`;
470      });
471  }
472
473  document.querySelectorAll("[data-copy-target]").forEach((button) => button.addEventListener("click", async () => {
474    const target = document.getElementById(button.dataset.copyTarget);
475    if (!target) return;
476    await navigator.clipboard.writeText(target.textContent.trim());
477    const original = button.textContent;
478    button.textContent = "✓";
479    window.setTimeout(() => { button.textContent = original; }, 1200);
480  }));
481
482  const dashboard = document.querySelector("[data-dashboard-root]");
483  if (!dashboard) return;
484  let dashboardHistory = null;
485  let activeDashboardHost = "";
486  let publicDemoDashboard = false;
487  let dashboardClient = null;
488  let dashboardGeneration = 0;
489  const clearCustomerView = () => {
490    activeDashboardHost = "";
491    window.AgentIdReportApi?.clear();
492    const content = document.querySelector("[data-dashboard-content]");
493    render({readiness: {measurement: {status: "measurement_failed"}}});
494    renderConnectors({connectors: ["a1", "a2", "b1"].map(id => ({id, status: "pending"}))});
495    renderTasks({tasks: []}); renderBilling(null); renderDecision(null);
496    renderHistory({snapshots: [], comparison: {status: "unavailable"}});
497    dashboardData = dashboardConnectors = dashboardTasks = dashboardBilling = dashboardDecision = dashboardHistory = null;
498    document.querySelector("[data-public-identity]")?.removeAttribute("href");
499    document.querySelectorAll("[data-metric]").forEach((node) => {
500      node.querySelectorAll("[data-value], [data-source], [data-state]").forEach((cell) => { cell.textContent = "—"; });
501    });
502    document.querySelectorAll("dialog[open]").forEach((dialog) => { dialog.close(); });
503    document.querySelectorAll("dialog:not([data-connector-dialog])").forEach((dialog) => dialog.remove());
504    const setupCommand = document.querySelector("[data-setup-command]");
505    if (setupCommand) setupCommand.textContent = "";
506    const input = document.getElementById("dashboard-audit-url");
507    if (input) input.value = "";
508    const quick = document.querySelector("[data-audit-quick-result]");
509    if (quick) { quick.textContent = ""; quick.hidden = true; }
510    content?.setAttribute("hidden", "");
511  };
512  const text = (selector, value) => {
513    const node = document.querySelector(selector);
514    if (node) node.textContent = value == null || value === "" ? "—" : String(value);
515  };
516  const score = (value) => Number.isFinite(Number(value)) ? Math.max(0, Math.min(100, Number(value))) : null;
517  const formatDate = (value) => {
518    if (!value) return "—";
519    const normalized = typeof value === "number" && value < 1e12 ? value * 1000 : value;
520    const date = new Date(normalized);
521    return Number.isNaN(date.valueOf()) ? String(value) : new Intl.DateTimeFormat(document.documentElement.lang || "en", {dateStyle:"medium",timeStyle:"short"}).format(date);
522  };
523  const dateKey = (value) => {
524    if (!value) return "";
525    const normalized = typeof value === "number" && value < 1e12 ? value * 1000 : value;
526    const date = new Date(normalized);
527    return Number.isNaN(date.valueOf()) ? "" : date.toISOString().slice(0,10);
528  };
529  const renderMetric = (id, metric) => {
530    const row = document.querySelector(`[data-metric="${id}"]`);
531    if (!row) return;
532    const state = row.querySelector("[data-state]");
533    const value = row.querySelector("[data-value]");
534    const source = row.querySelector("[data-source]");
535    const ready = metric?.status === "ready";
536    state.classList.toggle("ready", ready);
537    state.textContent = ready ? localized("Connected", "已接入") : localized("Pending", "待接入");
538    value.textContent = ready ? String(metric.value) : "—";
539    source.textContent = metric?.proof_level || "not_observed";
540  };
541  const renderConnectors = (payload) => {
542    dashboardConnectors = payload;
543    (payload?.connectors || []).forEach((connector) => {
544      const card = document.querySelector(`[data-connector="${connector.id}"]`);
545      if (!card) return;
546      const ready = connector.status === "ready";
547      const state = card.querySelector("[data-connector-state]");
548      const detail = card.querySelector("[data-connector-detail]");
549      if (state) {
550        state.textContent = ready ? localized("Connected", "已接入") : localized("Pending", "待接入");
551        state.classList.toggle("positive", ready);
552      }
553      if (detail) detail.textContent = ready
554        ? `${connector.observed_records} ${localized("observed records", "筆真實證據")}`
555        : localized("No observed evidence yet; not counted as zero", "尚未取得真實資料,不計為 0");
556    });
557  };
558  const fallbackConnectors = (data) => ({connectors:[
559    {id:"a1",status:data.outcomes?.ai_crawler_reads?.status,observed_records:data.outcome_details?.ai_reads?.total || 0},
560    {id:"a2",status:data.outcomes?.ai_citation_rate?.status,observed_records:data.outcome_details?.ai_citations?.total || 0},
561    {id:"b1",status:data.outcomes?.ai_attributed_conversions?.status,observed_records:data.outcome_details?.conversions?.total || 0},
562  ]});
563  // Keyed by the coverage plan's stable source ids. The server sends English;
564  // the ids let this render Chinese without the server knowing about locales.
565  const COVERAGE_COPY = {
566    readiness_audit: [
567      "A readiness audit", "一次可讀性稽核",
568      "Nothing to connect. Every audit of a verified host produces this.",
569      "不用接任何東西。每一次對已驗證網站的稽核都會產生這一筆。",
570    ],
571    server_log_connector: [
572      "CDN or server access logs", "CDN 或伺服器存取紀錄",
573      "Connect the log source that already records AI crawler requests.",
574      "把已經在記錄 AI 爬蟲請求的紀錄來源接上來。",
575    ],
576    citation_connector: [
577      "An existing citation export", "既有的引用資料匯出",
578      "Import observations you already collect elsewhere.",
579      "把你在別處已經在收的觀測資料匯進來。",
580    ],
581    answer_engine_probe: [
582      "An answer-engine probe run", "對答案引擎跑一次探測",
583      "An operator selects the engines and runs it explicitly.",
584      "由操作者選定引擎並明確執行。",
585    ],
586    provider_decision: [
587      "A provider decision report", "供應商的決策回報",
588      "An AI provider reports its decision through the provider decision endpoint.",
589      "由 AI 供應商透過決策回報端點送進來。",
590    ],
591    conversion_connector: [
592      "CRM or analytics conversion events", "CRM 或分析工具的轉換事件",
593      "Connect the system that already records conversions.",
594      "把已經在記錄轉換的系統接上來。",
595    ],
596  };
597
598  /* The connect dialog. Three steps for a two-step connector, two for an
599     import. The copy-the-request step exists because this service holds no
600     customer credential: the request has to be run by the customer, against
601     their own account. The dialog says that rather than leaving it to be
602     guessed. */
603  let connectSession = null;
604
605  const connectRequest = async (path, options) => {
606    const response = await fetch(path, {
607      method: options.method || "POST",
608      headers: {"Content-Type": "application/json"},
609      credentials: "same-origin",
610      body: JSON.stringify(options.body || {}),
611    });
612    const payload = await response.json().catch(() => ({}));
613    if (!response.ok || payload.ok === false) {
614      throw new Error(payload.error || `request_failed_${response.status}`);
615    }
616    return payload;
617  };
618
619  /* The probe dialog. Two gates before anything is spent: an engine has to be
620     chosen, and the cost has to be acknowledged. Neither is pre-ticked, and
621     the button stays disabled until both are answered — a billable action must
622     not be reachable by pressing return on a form. */
623  const openProbeDialog = () => {
624    const host = activeDashboardHost;
625    if (!host) return;
626    const dialog = document.createElement("dialog");
627    dialog.className = "connect-dialog";
628    const close = () => { dialog.close(); dialog.remove(); };
629
630    const head = document.createElement("header");
631    const title = document.createElement("h3");
632    title.textContent = localized("Run an answer-engine probe", "跑一次答案引擎探測");
633    const stage = document.createElement("p");
634    stage.className = "connect-stage";
635    stage.textContent = localized("A2 · Observed AI citation", "A2 · 實際引用");
636    const dismiss = document.createElement("button");
637    dismiss.type = "button"; dismiss.className = "connect-close";
638    dismiss.textContent = "×"; dismiss.addEventListener("click", close);
639    head.append(title, stage, dismiss);
640    dialog.append(head);
641
642    const note = document.createElement("p");
643    note.className = "connect-note";
644    note.textContent = localized(
645      "This queries external answer engines. Every run costs money, and nothing here runs on a schedule.",
646      "這會去問外部的答案引擎。每一次執行都會產生費用,而且這裡不會有任何排程自動執行。",
647    );
648    dialog.append(note);
649
650    const form = document.createElement("form");
651    form.className = "connect-form";
652    const error = document.createElement("p");
653    error.className = "connect-error";
654    error.hidden = true;
655    const engines = document.createElement("div");
656    engines.className = "probe-engines";
657    const acknowledge = document.createElement("label");
658    acknowledge.className = "probe-acknowledge";
659    const ack = document.createElement("input");
660    ack.type = "checkbox";
661    const ackText = document.createElement("span");
662    ackText.textContent = localized(
663      "I understand this run is billable.", "我了解這次執行會產生費用。",
664    );
665    acknowledge.append(ack, ackText);
666    const go = document.createElement("button");
667    go.type = "submit";
668    go.disabled = true;
669    go.textContent = localized("Run it", "執行");
670
671    const refreshEnabled = () => {
672      const chosen = engines.querySelector("input[type=checkbox]:checked");
673      go.disabled = !(chosen && ack.checked);
674    };
675    ack.addEventListener("change", refreshEnabled);
676
677    form.append(engines, acknowledge, go, error);
678    dialog.append(form);
679
680    connectRequest("/api/dashboard/connector/probe-engines", {body: {url: `https://${host}`}})
681      .then((payload) => {
682        const rows = (payload && payload.engines) || [];
683        if (!rows.length) {
684          const empty = document.createElement("p");
685          empty.className = "decision-empty";
686          empty.textContent = localized(
687            "No answer engine is configured yet. An operator has to add one before a probe can run.",
688            "目前沒有設定任何答案引擎。要先由操作者加上,才能執行探測。",
689          );
690          engines.append(empty);
691          return;
692        }
693        rows.forEach((engine) => {
694          const label = document.createElement("label");
695          const box = document.createElement("input");
696          box.type = "checkbox"; box.value = engine.id;
697          box.addEventListener("change", refreshEnabled);
698          const text = document.createElement("span");
699          text.textContent = engine.label || engine.id;
700          label.append(box, text);
701          engines.append(label);
702        });
703      })
704      .catch(() => {
705        error.textContent = localized("Could not read the engine list.", "讀不到引擎清單。");
706        error.hidden = false;
707      });
708
709    form.addEventListener("submit", async (event) => {
710      event.preventDefault();
711      error.hidden = true;
712      const chosen = [...engines.querySelectorAll("input[type=checkbox]:checked")].map((b) => b.value);
713      if (!chosen.length || !ack.checked) return;
714      go.disabled = true;
715      go.textContent = localized("Running…", "執行中…");
716      try {
717        const result = await connectRequest("/api/dashboard/connector/probe", {
718          body: {url: `https://${host}`, run: true, engines: chosen},
719        });
720        close();
721        if (result && result.intelligence) {
722          renderDecision({ok: true, intelligence: result.intelligence});
723        }
724      } catch (err) {
725        go.disabled = false;
726        go.textContent = localized("Run it", "執行");
727        error.textContent = localized("The run failed: ", "執行失敗:") + err.message;
728        error.hidden = false;
729      }
730    });
731
732    document.body.append(dialog);
733    dialog.showModal();
734  };
735
736  const openConnectDialog = (plan) => {
737    const host = activeDashboardHost || document.body.dataset.host || "";
738    if (!host) return;
739    const dialog = document.createElement("dialog");
740    dialog.className = "connect-dialog";
741    const close = () => { dialog.close(); dialog.remove(); connectSession = null; };
742
743    const render = (step, context) => {
744      dialog.replaceChildren();
745      const head = document.createElement("header");
746      const title = document.createElement("h3");
747      title.textContent = localized("Connect a source", "接上資料來源");
748      const stage = document.createElement("p");
749      stage.className = "connect-stage";
750      stage.textContent = plan.label || plan.stage;
751      const dismiss = document.createElement("button");
752      dismiss.type = "button"; dismiss.className = "connect-close";
753      dismiss.textContent = "×"; dismiss.addEventListener("click", close);
754      head.append(title, stage, dismiss);
755      dialog.append(head);
756
757      const error = document.createElement("p");
758      error.className = "connect-error";
759      error.hidden = true;
760      const fail = (message) => { error.textContent = message; error.hidden = false; };
761
762      if (step === "choose") {
763        const list = document.createElement("div");
764        list.className = "connect-choices";
765        plan.connectors.forEach((conn) => {
766          const choice = document.createElement("button");
767          choice.type = "button";
768          choice.className = `connect-choice${conn.billable ? " connect-choice-billable" : ""}`;
769          const name = document.createElement("b"); name.textContent = conn.label;
770          const need = document.createElement("span"); need.textContent = conn.you_need;
771          choice.append(name, need);
772          if (conn.billable) {
773            const cost = document.createElement("em");
774            cost.textContent = localized("Costs money each run", "每次執行都會計費");
775            choice.append(cost);
776          }
777          choice.addEventListener("click", () => {
778            connectSession = window.AgentIdConnectSource.createConnectSource({
779              request: connectRequest, localized,
780            });
781            connectSession.reset(host, conn.connector);
782            render("describe", conn);
783          });
784          list.append(choice);
785        });
786        dialog.append(list);
787      }
788
789      if (step === "describe") {
790        const conn = context;
791        const form = document.createElement("form");
792        form.className = "connect-form";
793        connectSession.fieldsFor(conn.connector).forEach((field) => {
794          const label = document.createElement("label");
795          const caption = document.createElement("span");
796          caption.textContent = localized(field.label[0], field.label[1]);
797          const input = document.createElement(field.type === "json" ? "textarea" : "input");
798          input.name = field.name;
799          if (field.type !== "json") input.type = "text";
800          const hint = document.createElement("small");
801          hint.textContent = localized(field.hint[0], field.hint[1]);
802          label.append(caption, input, hint);
803          form.append(label);
804        });
805        if (connectSession.needsWindow(conn.connector)) {
806          ["window_start", "window_end"].forEach((name, index) => {
807            const label = document.createElement("label");
808            const caption = document.createElement("span");
809            caption.textContent = index === 0 ? localized("From", "起") : localized("To", "迄");
810            const input = document.createElement("input");
811            input.type = "date"; input.name = name;
812            label.append(caption, input);
813            form.append(label);
814          });
815        }
816        const go = document.createElement("button");
817        go.type = "submit";
818        go.textContent = connectSession.needsPlan(conn.connector)
819          ? localized("Build the request", "產生請求")
820          : localized("Read it", "讀取內容");
821        form.append(go, error);
822        form.addEventListener("submit", async (event) => {
823          event.preventDefault();
824          error.hidden = true;
825          const values = Object.fromEntries(new FormData(form).entries());
826          try {
827            if (connectSession.needsPlan(conn.connector)) {
828              await connectSession.plan(values);
829              render("fetch", conn);
830            } else {
831              await connectSession.plan(values);
832              await connectSession.normalize();
833              render("confirm", conn);
834            }
835          } catch (err) {
836            fail(localized("Could not read that: ", "讀不到:") + err.message);
837          }
838        });
839        dialog.append(form);
840      }
841
842      if (step === "fetch") {
843        const conn = context;
844        const note = document.createElement("p");
845        note.className = "connect-note";
846        note.textContent = localized(
847          "Run this against your own account. We never see your credential, which is why you run it and not us.",
848          "請用你自己的帳號執行這個請求。我們不會看到你的憑證,所以由你執行、不是我們。",
849        );
850        const pre = document.createElement("pre");
851        pre.className = "connect-request";
852        pre.textContent = connectSession.requestPreview();
853        const copy = document.createElement("button");
854        copy.type = "button";
855        copy.textContent = localized("Copy", "複製");
856        copy.addEventListener("click", () => navigator.clipboard?.writeText(pre.textContent));
857        const form = document.createElement("form");
858        form.className = "connect-form";
859        const label = document.createElement("label");
860        const caption = document.createElement("span");
861        caption.textContent = localized("Paste what came back", "把回傳結果貼上");
862        const area = document.createElement("textarea");
863        area.name = "response";
864        label.append(caption, area);
865        const go = document.createElement("button");
866        go.type = "submit";
867        go.textContent = localized("Read it", "讀取內容");
868        form.append(label, go, error);
869        form.addEventListener("submit", async (event) => {
870          event.preventDefault();
871          error.hidden = true;
872          try {
873            await connectSession.normalize(area.value);
874            render("confirm", conn);
875          } catch (err) {
876            fail(localized("Could not read that: ", "讀不到:") + err.message);
877          }
878        });
879        dialog.append(note, pre, copy, form);
880      }
881
882      if (step === "confirm") {
883        const note = document.createElement("p");
884        note.className = "connect-note";
885        note.textContent = localized(
886          "This is what was extracted. Nothing is stored until you add it.",
887          "以下是讀出來的內容。在你按下加入之前,不會存任何東西。",
888        );
889        const table = document.createElement("dl");
890        table.className = "connect-summary";
891        connectSession.summary().forEach((row) => {
892          const dt = document.createElement("dt"); dt.textContent = row.label;
893          const dd = document.createElement("dd"); dd.textContent = String(row.value);
894          table.append(dt, dd);
895        });
896        const go = document.createElement("button");
897        go.type = "button";
898        go.textContent = localized("Add to evidence", "加入證據");
899        go.addEventListener("click", async () => {
900          error.hidden = true;
901          try {
902            const result = await connectSession.submit();
903            close();
904            if (result && result.intelligence) {
905              renderDecision({ok: true, intelligence: result.intelligence});
906            }
907          } catch (err) {
908            fail(localized("Could not store that: ", "存不進去:") + err.message);
909          }
910        });
911        dialog.append(note, table, go, error);
912      }
913    };
914
915    render("choose", null);
916    document.body.append(dialog);
917    dialog.showModal();
918  };
919
920  const renderDecision = (payload) => {
921    dashboardDecision = payload || {ok:true,intelligence:null};
922    const intelligence = dashboardDecision.intelligence;
923    const grid = document.querySelector("[data-decision-grid]");
924    if (!grid) return;
925    grid.replaceChildren();
926    const stageCopy = {
927      A0_crawlability:["公開存取","Public access","公開的 AI 擷取系統能否讀取網站?","Can public AI retrieval infrastructure access the website?"],
928      A1_retrieval:["實際讀取","Observed retrieval","是否觀測到已識別的 AI 爬蟲讀取網站?","Did an identified AI crawler retrieve the website?"],
929      A2_citation:["實際引用","Observed citation","已量測的 AI 回答是否引用網站?","Did a measured AI answer cite the website?"],
930      A3_consideration:["納入考量","Consideration","是否被列入供應商或方案的考量名單?","Was the website included in a measured consideration set?"],
931      A4_recommendation:["被推薦","Recommendation","是否在量測回答中被推薦?","Was the website recommended in a measured answer?"],
932      A5_preference_reason:["偏好原因","Preference reason","AI 是否提供偏好或排除網站的明確理由?","Was an explicit reason for or against preference observed?"],
933      B1_conversion:["商業結果","Business outcome","是否觀測到可歸因的詢價、訂單或其他商業結果?","Was an AI-attributed business outcome observed?"],
934    };
935    const stages = Object.keys(stageCopy);
936    const rows = intelligence?.stages || {};
937    const measuredCount = stages.filter((id) => rows[id]?.status === "measured").length;
938    const observedCount = stages.filter((id) => rows[id]?.status && rows[id].status !== "pending").length;
939    text("[data-decision-status]", intelligence
940      ? `${localized("Observed", "已觀測")} ${observedCount} / ${stages.length} · ${localized("sample gate met", "達樣本門檻")} ${measuredCount}`
941      : localized("Tenant evidence appears after sign-in", "登入後顯示租戶證據"));
942    const statusCopy = {
943      measured: localized("Measured", "已量測"),
944      insufficient_sample: localized("More samples needed", "樣本不足"),
945      pending: localized("Pending", "待取得"),
946    };
947    stages.forEach((id) => {
948      const stage = rows[id] || {status:"pending",numerator:0,denominator:0,sample_size:0,evidence:[]};
949      const status = statusCopy[stage.status] ? stage.status : "pending";
950      const details = document.createElement("details");
951      details.className = `decision-stage state-${status}`;
952      const summary = document.createElement("summary");
953      const head = document.createElement("div");
954      const code = document.createElement("span"); code.className = "decision-code"; code.textContent = id.split("_")[0];
955      const title = document.createElement("b"); title.textContent = localized(stageCopy[id][1], stageCopy[id][0]);
956      const state = document.createElement("em"); state.textContent = localized(statusCopy[status]);
957      head.append(code,title,state);
958      const question = document.createElement("p"); question.textContent = localized(stageCopy[id][3], stageCopy[id][2]);
959      const metric = document.createElement("strong");
960      metric.textContent = status === "measured" && Number.isFinite(Number(stage.rate))
961        ? `${Math.round(Number(stage.rate) * 100)}%`
962        : status === "insufficient_sample"
963          ? `${stage.numerator || 0}/${stage.denominator || 0} · ≥${stage.minimum_sample_size || 3}`
964          : "—";
965      summary.append(head,question,metric);
966      const evidenceList = document.createElement("div"); evidenceList.className = "decision-evidence-list";
967      const evidence = Array.isArray(stage.evidence) ? stage.evidence : [];
968      if (!evidence.length) {
969        const empty = document.createElement("p"); empty.className = "decision-empty";
970        empty.textContent = localized("No contract-valid evidence has been observed.", "尚未取得符合契約的證據。");
971        evidenceList.append(empty);
972        // "Nothing observed" is true and useless on its own: a customer cannot
973        // tell whether the product is broken, whether they have to do
974        // something, or whether they simply have to wait. The server's coverage
975        // plan answers that, and the source ids are stable so the copy can be
976        // localized here.
977        const plan = (intelligence?.coverage?.stages || []).find((row) => row.stage === id);
978        if (plan && plan.status !== "measured") {
979          const guide = document.createElement("div");
980          guide.className = `decision-coverage ${plan.customer_can_act ? "coverage-actionable" : "coverage-waiting"}`;
981          const lede = document.createElement("p");
982          lede.className = "decision-coverage-lede";
983          lede.textContent = plan.customer_can_act
984            ? localized("You can switch this on.", "這一項你可以自己開啟。")
985            : localized("This one waits on an AI provider reporting a decision.", "這一項要等 AI 供應商回報決策。");
986          guide.append(lede);
987          const list = document.createElement("ul");
988          list.className = "decision-coverage-sources";
989          (plan.fills_from || []).forEach((src) => {
990            const item = document.createElement("li");
991            const label = document.createElement("b");
992            label.textContent = localized(COVERAGE_COPY[src.source]?.[0] || src.label, COVERAGE_COPY[src.source]?.[1] || src.label);
993            const how = document.createElement("span");
994            how.textContent = localized(COVERAGE_COPY[src.source]?.[2] || src.requires, COVERAGE_COPY[src.source]?.[3] || src.requires);
995            item.append(label, how);
996            if (src.billable) {
997              const cost = document.createElement("em");
998              cost.className = "decision-coverage-cost";
999              cost.textContent = localized("Billable, never runs on its own", "會計費,不會自動執行");
1000              item.append(cost);
1001            }
1002            // Naming the connector and what it asks for turns "connect a log
1003            // source" into something the tenant can go and do today.
1004            (src.connectors || []).forEach((conn) => {
1005              const detail = document.createElement("span");
1006              detail.className = "decision-coverage-connector";
1007              detail.textContent = `${conn.label} · ${conn.you_need}`;
1008              item.append(detail);
1009            });
1010            list.append(item);
1011          });
1012          guide.append(list);
1013          if (!publicDemoDashboard && activeDashboardHost
1014              && (plan.fills_from || []).some((src) => src.billable)) {
1015            const probe = document.createElement("button");
1016            probe.type = "button";
1017            probe.className = "decision-coverage-connect decision-coverage-billable";
1018            probe.textContent = localized("Run a probe (billable)", "
1018跑一次探測(會計費)");
1019            probe.addEventListener("click", () => openProbeDialog());
1020            guide.append(probe);
1021          }
1022          // Some stages need no connector at all: the product can observe the
1023          // customer's own verified host by itself. Those get one button, not a
1024          // dialog, because there is nothing to configure or paste.
1025          if (!publicDemoDashboard && activeDashboardHost && (plan.actions || []).includes("public_audit")) {
1026            const run = document.createElement("button");
1027            run.type = "button";
1028            run.className = "decision-coverage-connect";
1029            run.textContent = localized("Observe it now", "現在觀測一次");
1030            run.addEventListener("click", async () => {
1031              const previous = run.textContent;
1032              run.disabled = true;
1033              run.textContent = localized("Observing…", "觀測中…");
1034              try {
1035                const result = await connectRequest("/api/dashboard/connector/submit", {
1036                  body: {url: `https://${activeDashboardHost}`, kind: "public_audit"},
1037                });
1038                if (result && result.intelligence) {
1039                  renderDecision({ok: true, intelligence: result.intelligence});
1040                  return;
1041                }
1042              } catch (_error) {
1043                run.textContent = localized("Could not observe it", "這次觀測失敗");
1044                return;
1045              } finally {
1046                run.disabled = false;
1047                if (run.textContent === localized("Observing…", "觀測中…")) run.textContent = previous;
1048              }
1049            });
1050            guide.append(run);
1051          }
1052          // Not on the public demo: there is no tenant there to store evidence for.
1053          if (plan.customer_can_act && (plan.connectors || []).length
1054              && !publicDemoDashboard && activeDashboardHost && window.AgentIdConnectSource) {
1055            const connect = document.createElement("button");
1056            connect.type = "button";
1057            connect.className = "decision-coverage-connect";
1058            connect.textContent = localized("Connect a source", "接上資料來源");
1059            connect.addEventListener("click", () => openConnectDialog(plan));
1060            guide.append(connect);
1061          }
1062          evidenceList.append(guide);
1063        }
1064      }
1065      evidence.forEach((row, index) => {
1066        const article = document.createElement("article");
1067        const heading = document.createElement("header");
1068        const source = document.createElement("b"); source.textContent = row.source || localized("Source unavailable", "未標示來源");
1069        const result = document.createElement("span"); result.textContent = row.value === true ? localized("Observed", "成立") : localized("Not observed", "未成立");
1070        heading.append(source,result);
1071        const facts = document.createElement("dl");
1072        const windowValue = row.window ? `${formatDate(row.window.start)} – ${formatDate(row.window.end)}` : "—";
1073        const factRows = [
1074          [localized("Method", "方法"), row.method || "—"],
1075          [localized("Window", "量測期間"), windowValue],
1076          [localized("Numerator / denominator", "分子 / 分母"), `${row.numerator ?? "—"} / ${row.denominator ?? "—"}`],
1077          [localized("Sample size", "樣本數"), row.sample_size ?? "—"],
1078          [localized("Observed at", "觀測時間"), formatDate(row.observed_at)],
1079          [localized("Attestation", "證據狀態"), row.attestation_status || "—"],
1080        ];
1081        factRows.forEach(([label,value]) => {
1082          const dt = document.createElement("dt"); dt.textContent = label;
1083          const dd = document.createElement("dd"); dd.textContent = String(value);
1084          facts.append(dt,dd);
1085        });
1086        const receipt = document.createElement("code"); receipt.textContent = row.receipt_hash || "—";
1087        const receiptLabel = document.createElement("small"); receiptLabel.textContent = localized("Receipt hash", "收據雜湊");
1088        const limitations = document.createElement("ul");
1089        (row.limitations || []).forEach((item) => { const li = document.createElement("li"); li.textContent = item; limitations.append(li); });
1090        article.append(heading,facts,receiptLabel,receipt,limitations);
1091        evidenceList.append(article);
1092      });
1093      details.append(summary,evidenceList);
1094      grid.append(details);
1095    });
1096  };
1097  const renderTasks = (payload) => {
1098    dashboardTasks = payload;
1099    const grid = document.querySelector("[data-task-grid]");
1100    if (!grid) return;
1101    grid.replaceChildren();
1102    const tasks = payload?.tasks || [];
1103    if (!tasks.length) {
1104      const empty = document.createElement("p");
1105      empty.className = "task-empty";
1106      empty.textContent = localized("No bounded file changes are waiting for action.", "目前沒有等待處理的限定檔案變更。");
1107      grid.append(empty);
1108      return;
1109    }
1110    tasks.forEach((task) => {
1111      const article = document.createElement("article");
1112      article.className = "task-item";
1113      const header = document.createElement("header");
1114      const path = document.createElement("b"); path.textContent = task.path || "—";
1115      const state = document.createElement("span"); state.textContent = task.state || "—";
1116      header.append(path, state);
1117      const action = document.createElement("p");
1118      action.textContent = task.action || localized("Review bounded file change", "檢視限定檔案變更");
1119      article.append(header, action);
1120      if (task.customer_command) {
1121        const command = document.createElement("pre"); command.textContent = task.customer_command;
1122        const copy = document.createElement("button");
1123        copy.type = "button"; copy.className = "task-copy";
1124        copy.textContent = localized("Copy customer command", "複製客戶端命令");
1125        copy.addEventListener("click", async () => {
1126          await navigator.clipboard.writeText(task.customer_command);
1127          copy.textContent = "✓";
1128          window.setTimeout(() => { copy.textContent = localized("Copy customer command", "複製客戶端命令"); }, 1200);
1129        });
1130        article.append(command, copy);
1131      }
1132      grid.append(article);
1133    });
1134  };
1135  const safeStripeCheckoutUrl = (result, planId, host) => {
1136    const sessionId = String(result?.checkout_session_id || "");
1137    let destination;
1138    try {
1139      destination = new URL(result?.checkout_url || "");
1140    } catch {
1141      return "";
1142    }
1143    if (
1144      result?.ok !== true ||
1145      result.provider !== "stripe" ||
1146      result.plan_id !== planId ||
1147      result.host !== host ||
1148      result.payment_state !== "pending_provider_confirmation" ||
1149      !/^cs_[A-Za-z0-9_]+$/.test(sessionId) ||
1150      destination.protocol !== "https:" ||
1151      destination.hostname !== "checkout.stripe.com" ||
1152      destination.port ||
1153      destination.username ||
1154      destination.password ||
1155      destination.pathname === "/"
1156    ) return "";
1157    return destination.href;
1158  };
1159  const renderBilling = (payload) => {
1160    dashboardBilling = payload;
1161    const panel = document.querySelector("[data-billing-panel]");
1162    const planGrid = document.querySelector("[data-billing-plan-grid]");
1163    const list = document.querySelector("[data-billing-list]");
1164    const manage = document.querySelector("[data-billing-manage]");
1165    const message = document.querySelector("[data-billing-message]");
1166    if (!panel || !planGrid || !list || !manage || !message) return;
1167    const hidden = publicDemoDashboard || !payload;
1168    panel.toggleAttribute("hidden", hidden);
1169    planGrid.replaceChildren();
1170    list.replaceChildren();
1171    message.textContent = "";
1172    if (hidden) return;
1173    const plans = payload.plans || [];
1174    const accessCopy = {
1175      included: localized("Included", "已包含"),
1176      active: localized("Active", "使用中"),
1177      payment_recovery_required: localized("Payment needs attention", "付款需要處理"),
1178      checkout_available: localized("Available", "可啟用"),
1179      approval_required: localized("Not available", "暫不可用"),
1180      requested: localized("Not available", "暫不可用"),
1181      configuration_pending: localized("Coming soon", "設定中"),
1182    };
1183    const maturityCopy = {
1184      GA: localized("Generally available", "正式提供"),
1185      "Early Access": localized("Early Access", "搶先使用"),
1186      "Enterprise Pilot": localized("Enterprise Pilot", "企業試點"),
1187    };
1188    const runPlanAction = async (plan, button) => {
1189      if (!activeDashboardHost || publicDemoDashboard || button.disabled || plan.action !== "checkout") return;
1190      const epoch = dashboardGeneration;
1191      const host = activeDashboardHost;
1192      button.disabled = true;
1193      message.textContent = localized("Creating a secure Stripe Checkout session…", "正在建立 Stripe 安全付款頁…");
1194      try {
1195        const result = await fetchJson("/api/dashboard/billing/checkout", {
1196          method:"POST",
1197          headers:{"Content-Type":"application/json"},
1198          body:JSON.stringify({
1199            url:`https://${host}`,
1200            plan:plan.id,
1201            source:"dashboard_pricing",
1202          }),
1203        });
1204        if (epoch !== dashboardGeneration) return;
1205        const checkoutUrl = safeStripeCheckoutUrl(result, plan.id, host);
1206        if (!checkoutUrl) {
1207          message.textContent = localized("Stripe returned an unusable checkout link. No checkout page was opened and no payment is confirmed. Refresh billing status before trying again.", "Stripe 回傳的付款連結無法安全使用;未開啟付款頁,付款也尚未確認。請重新整理帳務狀態後再試。");
1208          button.disabled = false;
1209          return;
1210        }
1211        message.textContent = localized("Opening Stripe. The plan changes only after a signed provider confirmation is received.", "即將前往 Stripe。只有收到簽章付款確認後,方案狀態才會變更。");
1212        window.location.assign(checkoutUrl);
1213      } catch (error) {
1214        if (epoch !== dashboardGeneration) return;
1215        if (error.status === 401) { showDashboardError(error); return; }
1216        const managementRequired = ["active_subscription_exists", "subscription_requires_management"].includes(error.code);
1217        message.textContent = managementRequired
1218          ? localized("An existing subscription needs billing management. No new checkout page was opened.", "現有訂閱需要透過帳務管理處理;未開啟新的付款頁。")
1219          : localized("The server did not provide an available checkout session. No payment is confirmed. Refresh billing status before trying again.", "伺服器未提供可用付款工作階段,付款尚未確認。請重新整理帳務狀態後再試。");
1220        button.disabled = false;
1221      }
1222    };
1223    plans.forEach((plan) => {
1224      const article = document.createElement("article");
1225      article.className = `billing-plan${plan.recommended ? " recommended" : ""}`;
1226      article.dataset.planId = plan.id;
1227      const head = document.createElement("header");
1228      const title = document.createElement("div");
1229      const name = document.createElement("h3"); name.textContent = plan.name;
1230      const maturity = document.createElement("span");
1231      maturity.className = "billing-maturity";
1232      maturity.textContent = maturityCopy[plan.maturity] || plan.maturity || "";
1233      title.append(name, maturity);
1234      const state = document.createElement("strong");
1235      state.className = `billing-access-state state-${plan.access_state || "unknown"}`;
1236      state.textContent = accessCopy[plan.access_state] || plan.access_state || "—";
1237      head.append(title, state);
1238      const price = document.createElement("p"); price.className = "billing-price";
1239      price.textContent = Number(plan.price || 0) === 0
1240        ? (localized("Free", "免費"))
1241        : `US$${Number(plan.price).toFixed(0)} / ${localized("month", "月")}`;
1242      const unit = document.createElement("small");
1243      unit.textContent = localized("per verified domain", "每個已驗證網域");
1244      price.append(document.createElement("br"), unit);
1245      const features = document.createElement("ul"); features.className = "billing-features";
1246      (plan.includes || []).forEach((feature) => {
1247        const item = document.createElement("li"); item.textContent = feature; features.append(item);
1248      });
1249      article.append(head, price, features);
1250      if (!["included", "active"].includes(plan.access_state)) {
1251        const action = document.createElement("button");
1252        action.type = "button";
1253        action.className = `btn ${plan.action === "manage" ? "primary" : "secondary"} billing-plan-action`;
1254        action.textContent = plan.action === "checkout"
1255          ? localized("Continue to Stripe", "透過 Stripe 啟用")
1256          : plan.action === "manage"
1257            ? localized("Resolve payment in Stripe", "在 Stripe 處理付款")
1258            : localized("Not available", "暫不可用");
1259        action.disabled = plan.access_state === "configuration_pending" || !["checkout", "manage"].includes(plan.action);
1260        action.addEventListener("click", () => {
1261          if (plan.action === "manage") {
1262            manage.click();
1263            return;
1264          }
1265          if (plan.action === "checkout") runPlanAction(plan, action);
1266        });
1267        article.append(action);
1268      }
1269      planGrid.append(article);
1270    });
1271    const subscriptions = payload.subscriptions || [];
1272    if (!subscriptions.length) {
1273      const empty = document.createElement("p");
1274      empty.className = "billing-empty";
1275      empty.textContent = localized("No paid subscription is active. Public audits remain available; a verified website identity needs the Identity plan.", "目前沒有付費訂閱。公開檢測仍可使用;驗證網站身分需要 Identity 方案。");
1276      list.append(empty);
1277    }
1278    const stateCopy = {
1279      renews: localized("Renews", "持續續訂"),
1280      payment_issue: localized("Payment needs attention", "付款需要處理"),
1281      scheduled_to_cancel: localized("Cancellation scheduled", "已排定取消"),
1282      canceled: localized("Canceled", "已取消"),
1283      not_renewing: localized("Not renewing", "不續訂"),
1284    };
1285    const providerCopy = {
1286      stripe: "Stripe",
1287      google_marketplace: "Google Cloud Marketplace",
1288    };
1289    subscriptions.forEach((subscription) => {
1290      const article = document.createElement("article");
1291      article.className = "billing-row";
1292      const head = document.createElement("header");
1293      const title = document.createElement("div");
1294      const name = document.createElement("h3");
1295      name.textContent = String(subscription.plan_id || "plan").replaceAll("_", " ");
1296      const provider = document.createElement("span");
1297      provider.textContent = providerCopy[subscription.provider] || subscription.provider || (localized("Billing provider", "付款平台"));
1298      title.append(name, provider);
1299      const status = document.createElement("strong");
1300      status.className = `billing-state state-${subscription.renewal_state || "unknown"}`;
1301      status.textContent = stateCopy[subscription.renewal_state] || subscription.status || (localized("Pending", "待確認"));
1302      head.append(title, status);
1303      const detail = document.createElement("dl");
1304      const detailRows = [
1305        [localized("Access status", "服務狀態"), subscription.status || "—"],
1306        [localized("Current period ends", "目前週期結束"), formatDate(subscription.current_period_end)],
1307        [localized("Access ends", "權限停止時間"), formatDate(subscription.access_ends_at)],
1308      ];
1309      detailRows.forEach(([label, value]) => {
1310        const dt = document.createElement("dt"); dt.textContent = label;
1311        const dd = document.createElement("dd"); dd.textContent = value;
1312        detail.append(dt, dd);
1313      });
1314      article.append(head, detail);
1315      if (subscription.payment_recovery_required) {
1316        const recovery = document.createElement("p");
1317        recovery.className = "billing-recovery";
1318        const deadline = subscription.payment_recovery_deadline
1319          ? formatDate(subscription.payment_recovery_deadline)
1320          : "";
1321        recovery.textContent = subscription.status === "suspended"
1322          ? localized(
1323            "Payment still needs attention. Update the payment method and pay the latest invoice in Stripe if requested. Service resumes after Stripe confirms the subscription is active.",
1324            "付款仍需處理。請在 Stripe 更新付款方式,並依提示支付最新帳單;Stripe 確認訂閱恢復有效後,服務才會恢復。",
1325          )
1326          : deadline
1327            ? localized(
1328              "Payment was not completed. Resolve it in Stripe by {deadline} to avoid interruption.",
1329              "付款未完成。請在 {deadline} 前於 Stripe 完成處理,避免服務中斷。",
1330            ).replace("{deadline}", deadline)
1331            : localized(
1332              "Payment was not completed. Resolve it in Stripe to avoid interruption.",
1333              "付款未完成。請前往 Stripe 完成處理,避免服務中斷。",
1334            );
1335        article.append(recovery);
1336      }
1337      const features = subscription.entitled_features || [];
1338      if (features.length) {
1339        const featureList = document.createElement("ul");
1340        featureList.className = "billing-features";
1341        features.forEach((feature) => {
1342          const item = document.createElement("li"); item.textContent = feature; featureList.append(item);
1343        });
1344        article.append(featureList);
1345      }
1346      if (subscription.provider === "google_marketplace") {
1347        const note = document.createElement("p");
1348        note.className = "billing-provider-note";
1349        note.textContent = localized("This plan is managed in Google Cloud Marketplace. Change or cancel it from Google Cloud Billing.", "此方案由 Google Cloud Marketplace 管理,請在 Google Cloud 帳務中調整或取消。");
1350        article.append(note);
1351      }
1352      list.append(article);
1353    });
1354    const paymentRecoveryRequired = subscriptions.some((item) => item.payment_recovery_required);
1355    manage.toggleAttribute("hidden", !payload.manage_available);
1356    manage.classList.toggle("primary", paymentRecoveryRequired);
1357    manage.classList.toggle("secondary", !paymentRecoveryRequired);
1358    manage.textContent = paymentRecoveryRequired
1359      ? localized("Resolve payment in Stripe", "在 Stripe 處理付款")
1360      : (localized("Manage or cancel Stripe subscription", "管理或取消 Stripe 訂閱"));
1361    manage.onclick = async () => {
1362      if (!activeDashboardHost || publicDemoDashboard) return;
1363      const epoch = dashboardGeneration;
1364      manage.disabled = true;
1365      message.textContent = localized("Creating a secure billing management link…", "正在建立安全的付款管理連結…");
1366      try {
1367        const result = await fetchJson("/api/dashboard/billing/portal", {
1368          method:"POST",
1369          headers:{"Content-Type":"application/json"},
1370          body:JSON.stringify({url:`https://${activeDashboardHost}`}),
1371        });
1372        if (epoch !== dashboardGeneration) return;
1373        message.textContent = paymentRecoveryRequired
1374          ? localized(
1375            "Opening Stripe to resolve the payment. Access resumes only after Stripe confirms the subscription is active.",
1376            "即將前往 Stripe 處理付款。只有在 Stripe 確認訂閱恢復有效後,權限才會恢復。",
1377          )
1378          : (localized("Opening Stripe. Access changes only after the provider confirms the update.", "即將前往 Stripe。完成後仍須等待付款平台確認,權限狀態才會更新。"));
1379        window.location.assign(result.portal_url);
1380      } catch (error) {
1381        if (epoch !== dashboardGeneration) return;
1382        if (error.status === 401 || error.status === 403) { showDashboardError(error); return; }
1383        message.textContent = `${localized("Unable to open billing management", "無法開啟付款管理")}: ${error.message}`;
1384        manage.disabled = false;
1385      }
1386    };
1387  };
1388  const metricValue = (snapshot, group, id) => snapshot?.[group]?.[id]?.value;
1389  const fallbackHistory = (data) => {
1390    const axes = ["seo","aeo","geo","ago"];
1391    const baselineScores = Object.fromEntries(axes.map((axis) => [axis, {value: score(data.historical_baseline?.dimensions?.[axis]?.score),status:"ready",proof_level:data.historical_baseline?.claim_level || "observed"}]));
1392    const currentScores = Object.fromEntries(axes.map((axis) => [axis, {value: score(data.readiness?.dimensions?.[axis]?.score),status:"ready",proof_level:"observed_public_fetch"}]));
1393    const comparisonScores = Object.fromEntries(axes.map((axis) => {
1394      const before = baselineScores[axis].value; const after = currentScores[axis].value;
1395      const comparable = Number.isFinite(before) && Number.isFinite(after);
1396      return [axis,{baseline:before,current:after,delta:comparable ? after-before : null,comparable}];
1397    }));
1398    return {ok:true,schema:"agentx-customer-dashboard-history-v1",host:data.customer?.host || "",missing_data_policy:"pending_not_zero",snapshots:[
1399      {snapshot_id:data.historical_baseline?.run_id || "baseline",observed_at:data.historical_baseline?.captured_at || "",scores:baselineScores,outcomes:{}},
1400      {snapshot_id:"live-public-fetch",observed_at:dateKey(data.readiness?.observed_at),scores:currentScores,outcomes:{}},
1401    ],comparison:{status:"ready",scores:comparisonScores,outcomes:{}},non_claims:["does_not_prove_incrementality_or_causality"]};
1402  };
1403  const renderHistory = (history) => {
1404    dashboardHistory = history;
1405    const snapshots = history?.snapshots || [];
1406    const content = document.querySelector("[data-history-content]");
1407    const empty = document.querySelector("[data-history-empty]");
1408    const available = snapshots.length > 0;
1409    const comparableWindow = snapshots.length >= 2;
1410    content?.toggleAttribute("hidden", !available);
1411    empty?.toggleAttribute("hidden", comparableWindow);
1412    if (empty) empty.textContent = snapshots.length === 0
1413      ? (localized("No measurements exist in the selected dates.", "所選日期內沒有量測資料。"))
1414      : (localized("Only one period is available. A second inspection is required for comparison.", "目前只有一期資料;下一次巡檢後才能計算前後變化。"));
1415    const trendGrid = document.querySelector("[data-trend-grid]");
1416    trendGrid?.replaceChildren();
1417    const comparisonGrid = document.querySelector("[data-comparison-grid]");
1418    comparisonGrid?.replaceChildren();
1419    comparisonGrid?.toggleAttribute("hidden", !comparableWindow);
1420    if (!available) return;
1421    ["seo","aeo","geo","ago"].forEach((axis) => {
1422      const article = document.createElement("article"); article.className = "trend-card";
1423      const header = document.createElement("header");
1424      const heading = document.createElement("h3"); heading.textContent = axis.toUpperCase();
1425      const current = metricValue(snapshots.at(-1), "scores", axis);
1426      const currentText = document.createElement("b"); currentText.textContent = Number.isFinite(current) ? String(current) : "—";
1427      header.append(heading,currentText);
1428      const bars = document.createElement("div"); bars.className = "trend-bars";
1429      snapshots.forEach((snapshot) => {
1430        const value = metricValue(snapshot,"scores",axis);
1431        const bar = document.createElement("i"); bar.className = "trend-point";
1432        bar.style.setProperty("--value", Number.isFinite(value) ? Math.max(0,Math.min(100,value)) : 0);
1433        bar.title = `${snapshot.observed_at || (localized("Baseline", "基準"))}: ${Number.isFinite(value) ? value : (localized("Pending", "待取得"))}`;
1434        bars.append(bar);
1435      });
1436      const labels = document.createElement("div"); labels.className = "trend-labels";
1437      const first = document.createElement("span"); first.textContent = snapshots[0]?.observed_at || (localized("Baseline", "基準"));
1438      const last = document.createElement("span"); last.textContent = snapshots.at(-1)?.observed_at || (localized("Current", "目前"));
1439      labels.append(first,last); article.append(header,bars,labels); trendGrid?.append(article);
1440    });
1441    ["seo","aeo","geo","ago"].forEach((axis) => {
1442      const row = history.comparison?.scores?.[axis] || {};
1443      const item = document.createElement("article"); item.className = "comparison-item";
1444      const label = document.createElement("span"); label.textContent = `${axis.toUpperCase()} ${localized("change", "前後變化")}`;
1445      const value = document.createElement("b");
1446      value.textContent = row.comparable ? `${row.baseline} → ${row.current} (${row.delta > 0 ? "+" : ""}${row.delta})` : (localized("Not comparable yet", "尚不可比較"));
1447      if (row.comparable && row.delta > 0) value.className = "positive";
1448      if (row.comparable && row.delta < 0) value.className = "negative";
1449      item.append(label,value); comparisonGrid?.append(item);
1450    });
1451  };
1452  const render = (data) => {
1453    dashboardData = data;
1454    const failedMeasurement = data.readiness?.measurement?.status === "measurement_failed";
1455    const current = score(data.readiness?.overall);
1456    const baseline = score(data.historical_baseline?.overall);
1457    const delta = data.comparison?.overall_delta;
1458    const ring = document.querySelector("[data-overall-ring]");
1459    if (ring) ring.style.setProperty("--score", current ?? 0);
1460    text("[data-overall]", failedMeasurement ? null : current);
1461    text("[data-baseline]", baseline);
1462    text("[data-delta]", Number.isFinite(delta) ? `${delta > 0 ? "+" : ""}${delta}` : "—");
1463    text("[data-observed-at]", formatDate(data.readiness?.observed_at));
1464    const maturity = failedMeasurement
1465      ? localized("We could not measure this website yet", "目前無法量測網站")
1466      : current >= 85 ? localized("AI foundations are mature", "AI 基礎成熟") : current >= 60 ? localized("AI foundations exist with clear gaps", "AI 基礎已建立,仍有明確缺口") : localized("AI foundations need priority work", "AI 基礎仍需優先補強");
1467    text("[data-summary-title]", maturity);
1468    const customerName = data.customer?.name || data.customer?.host || "the website";
1469    text("[data-summary-copy]", failedMeasurement
1470      ? measurementFailureCopy({measurement:data.readiness?.measurement})
1471      : localized("The mother service refetches {customer}'s public website. The score measures technical readiness, not AI ranking or conversion causality.", "分數來自母體重新讀取 {customer} 的公開網站,不使用手動填寫。它衡量技術準備度,不代表 AI 排名或成交因果。").replace("{customer}", customerName));
1472    text("[data-agent-id]", data.customer?.agent_id);
1473    text("[data-customer-name]", data.customer?.name || data.customer?.host);
1474    text("[data-identity-status]", data.customer?.verification_status);
1475    text("[data-dashboard-status]", data.customer?.verification_status || (localized("Identity pending", "身分待確認")));
1476    const identityLink = document.querySelector("[data-public-identity]");
1477    if (identityLink && data.customer?.public_identity_url) identityLink.href = data.customer.public_identity_url;
1478    text("[data-run-id]", data.historical_baseline?.run_id);
1479    text("[data-result-hash]", data.historical_baseline?.result_hash);
1480    text("[data-captured-at]", formatDate(data.historical_baseline?.captured_at));
1481    ["seo","aeo","geo","ago"].forEach((axis) => {
1482      const row = data.readiness?.dimensions?.[axis] || {};
1483      const value = failedMeasurement ? null : score(row.score);
1484      text(`[data-axis-score="${axis}"]`, value);
1485      const fill = document.querySelector(`[data-axis-fill="${axis}"]`);
1486      if (fill) fill.style.width = `${value ?? 0}%`;
1487      text(`[data-axis-detail="${axis}"]`, failedMeasurement ? localized("Not measured","未量測") : `${row.pass || 0} / ${row.partial || 0} / ${row.pending || 0}`);
1488    });
1489    Object.entries(data.outcomes || {}).forEach(([id, metric]) => renderMetric(id, metric));
1490    text("[data-read-total]", data.outcome_details?.ai_reads?.total ?? "—");
1491    text("[data-citation-total]", data.outcome_details?.ai_citations?.total ?? "—");
1492    text("[data-conversion-total]", data.outcome_details?.conversions?.ai_attributed ?? "—");
1493    const tabs = [...document.querySelectorAll("[data-check-axis]")];
1494    const renderChecks = (axis) => {
1495      tabs.forEach((tab) => tab.setAttribute("aria-selected", String(tab.dataset.checkAxis === axis)));
1496      const container = document.querySelector("[data-check-details]");
1497      if (!container) return;
1498      container.replaceChildren();
1499      const rows = data.checks_by_axis?.[axis] || [];
1500      text("[data-check-count]", `${rows.length} ${localized(rows.length === 1 ? "check" : "checks")}`);
1501      rows.forEach((item) => {
1502        const details = document.createElement("details");
1503        details.className = `check-item status-${item.status || "pending"}`;
1504        const summary = document.createElement("summary");
1505        const state = document.createElement("span"); state.className = "check-state"; state.textContent = localized(item.status || "pending");
1506        const title = document.createElement("b"); title.textContent = localized(item.label || item.id);
1507        const importance = document.createElement("em"); importance.textContent = localized(item.importance || "medium");
1508        summary.append(state, title, importance);
1509        const body = document.createElement("div"); body.className = "check-body";
1510        const evidence = document.createElement("p"); evidence.innerHTML = `<strong>${localized("Evidence", "檢證")}</strong>`;
1511        evidence.append(document.createTextNode(` ${item.evidence || "No public evidence observed"}`));
1512        const fix = document.createElement("p"); fix.innerHTML = `<strong>${localized("Action", "改善")}</strong>`;
1513        fix.append(document.createTextNode(` ${item.recommended_fix || localized("No action required", "目前不需調整")}`));
1514        body.append(evidence, fix); details.append(summary, body);
1514 container.append(details);
1515      });
1516    };
1517    tabs.forEach((tab) => { tab.onclick = () => renderChecks(tab.dataset.checkAxis); });
1518    renderChecks(tabs.find((tab) => tab.getAttribute("aria-selected") === "true")?.dataset.checkAxis || "seo");
1519    const list = document.querySelector("[data-priorities]");
1520    list.replaceChildren();
1521    (data.priorities || []).slice(0, 5).forEach((item) => {
1522      const article = document.createElement("article");
1523      article.className = "priority";
1524      const axis = document.createElement("span"); axis.className = "priority-axis"; axis.textContent = String(item.axis || "").toUpperCase();
1525      const copy = document.createElement("div");
1526      const title = document.createElement("b"); title.textContent = localized(item.label || item.id);
1527      const detail = document.createElement("p"); detail.textContent = item.recommended_fix || item.evidence || "Review evidence";
1528      copy.append(title, detail);
1529      const importance = document.createElement("span"); importance.className = "importance"; importance.textContent = localized(item.importance || "medium");
1530      article.append(axis, copy, importance); list.append(article);
1531    });
1532    document.querySelector("[data-dashboard-loading]")?.setAttribute("hidden", "");
1533    document.querySelector("[data-dashboard-content]")?.removeAttribute("hidden");
1534  };
1535  const fetchJson = async (path, options={}) => {
1536    const response = await fetch(path, {...options,headers:{Accept:"application/json",...(options.headers || {})}});
1537    const data = await response.json();
1538    if (!response.ok || !data.ok) {
1539      const error = new Error(data.error || `HTTP ${response.status}`);
1540      error.status = response.status;
1541      error.code = data.error || "";
1542      throw error;
1543    }
1544    return data;
1545  };
1546  const showHistoryError = (error) => {
1547    renderHistory({snapshots:[],comparison:{status:"unavailable"}});
1548    const empty = document.querySelector("[data-history-empty]");
1549    if (empty) empty.textContent = `${localized("Unable to load history", "歷史資料載入失敗")}: ${error.message}`;
1550  };
1551  const loadCustomerSite = async (host) => {
1552    if (!dashboardClient) throw new Error("dashboard_client_not_ready");
1553    const epoch = ++dashboardGeneration;
1554    clearCustomerView();
1555    document.querySelector("[data-dashboard-loading]")?.removeAttribute("hidden");
1556    let bundle;
1557    try { bundle = await dashboardClient.loadSite(host); }
1558    catch (error) { if (epoch === dashboardGeneration) throw error; return; }
1559    if (epoch !== dashboardGeneration) return;
1560    const page = new URL(window.location.href);
1561    page.searchParams.set("site", bundle.host);
1562    page.searchParams.delete("login");
1563    window.history.replaceState({}, "", page);
1564    return renderCustomerBundle(bundle);
1565  };
1566  const historyQuery = (host, format="") => {
1567    const params = new URLSearchParams({url:`https://${host}`});
1568    const from = document.querySelector("[data-history-from]")?.value;
1569    const to = document.querySelector("[data-history-to]")?.value;
1570    if (from) params.set("from",from); if (to) params.set("to",to); if (format) params.set("format",format);
1571    return params.toString();
1572  };
1573  const loadHistory = async (host) => {
1574    const epoch = dashboardGeneration;
1575    if (!host) return;
1576    try {
1577      const payload = await fetchJson(`/api/dashboard/history?${historyQuery(host)}`);
1578      if (epoch === dashboardGeneration && host === activeDashboardHost) renderHistory(payload);
1579    } catch (error) {
1580      if (epoch === dashboardGeneration && host === activeDashboardHost) {
1581        if (error.status === 401 || error.status === 403) showDashboardError(error);
1582        else showHistoryError(error);
1583      }
1584    }
1585  };
1586  const dashboardReasonText = (detail="") => {
1587    const source = /dashboard_session_invalid_or_expired|HTTP 401/i.test(String(detail || ""))
1588      ? "This sign-in link has expired. Request a new one-time link."
1589      : "We could not confirm this sign-in. Request a new one-time link.";
1590    return window.XAgentI18n?.t ? window.XAgentI18n.t(source) : source;
1591  };
1592  const setDashboardMode = (mode, detail="") => {
1593    document.body.dataset.dashboardMode = mode;
1594    const label = document.querySelector("[data-dashboard-mode-label]");
1595    if (label) label.textContent = {
1596      demo: localized("PUBLIC DEMO", "公開示範"),
1597      customer: localized("CUSTOMER", "客戶模式"),
1598      login_required: localized("SIGN-IN REQUIRED", "需要登入"),
1599      initializing: localized("INITIALIZING", "確認中"),
1600    }[mode] || mode.toUpperCase();
1601    document.querySelector("[data-dashboard-demo-banner]")?.toggleAttribute("hidden", mode !== "demo");
1602    document.querySelector("[data-dashboard-auth-state]")?.toggleAttribute("hidden", mode !== "login_required");
1603    document.querySelector("[data-dashboard-logout]")?.toggleAttribute("hidden", mode !== "customer");
1604    document.querySelector("[data-site-picker-wrap]")?.toggleAttribute("hidden", mode !== "customer");
1605    document.querySelector("[data-dashboard-status]")?.toggleAttribute("hidden", mode !== "customer");
1606    if (mode === "login_required") {
1607      document.querySelector("[data-dashboard-content]")?.setAttribute("hidden", "");
1608      document.querySelector("[data-dashboard-loading]")?.setAttribute("hidden", "");
1609      const reason = document.querySelector("[data-dashboard-auth-reason]");
1610      if (reason) reason.textContent = dashboardReasonText(detail);
1611      activeDashboardHost = "";
1612      dashboardHistory = null;
1613    }
1614  };
1615  const renderCustomerBundle = (bundle) => {
1616    activeDashboardHost = bundle.host;
1617    render(bundle.data);
1618    renderConnectors(bundle.connectors);
1619    renderTasks(bundle.tasks);
1620    renderBilling(bundle.billing);
1621    renderDecision(bundle.decision);
1622    window.AgentIdReportApi.publish("customer", bundle.host, bundle.data);
1623    const auditInput = document.getElementById("dashboard-audit-url");
1624    if (auditInput) auditInput.value = `https://${bundle.host}`;
1625    return loadHistory(bundle.host);
1626  };
1627  const loadDashboard = async () => {
1628    const epoch = ++dashboardGeneration;
1629    clearCustomerView();
1630    if (!window.AgentIdDashboardClient) throw new Error("dashboard_client_not_loaded");
1631    dashboardClient = window.AgentIdDashboardClient.createDashboardClient({request:fetchJson});
1632    const requestedSite = new URLSearchParams(window.location.search).get("site") || "";
1633    let result;
1634    try { result = await dashboardClient.bootstrap(window.location.pathname, requestedSite); }
1635    catch (error) { if (epoch === dashboardGeneration) throw error; return; }
1636    if (epoch !== dashboardGeneration) return;
1637    if (result.mode === "demo") {
1638      setDashboardMode("demo");
1639      publicDemoDashboard = true;
1640      activeDashboardHost = result.selectedHost;
1641      document.querySelectorAll("[data-history-form] input, [data-history-form] button[type=submit]").forEach((node) => { node.disabled = true; });
1642      document.querySelector("[data-history-demo-note]")?.removeAttribute("hidden");
1643      const auditInput = document.getElementById("dashboard-audit-url");
1644      if (auditInput) auditInput.value = `https://${activeDashboardHost}`;
1645      render(result.data); renderConnectors(fallbackConnectors(result.data)); renderTasks({ok:true,tasks:[]}); renderBilling(null); renderDecision(null); renderHistory(fallbackHistory(result.data));
1646      window.AgentIdReportApi.publish("demo", result.selectedHost, result.data);
1647      return;
1648    }
1649    if (result.mode === "login_required") {
1650      setDashboardMode("login_required", result.reason);
1651      return;
1652    }
1653    if (result.mode !== "customer") throw new Error("dashboard_mode_invalid");
1654    setDashboardMode("customer");
1655    publicDemoDashboard = false;
1656    const sites = result.sites || [];
1657    const picker = document.querySelector("[data-site-picker]");
1658    picker.replaceChildren(...sites.map((site) => {
1659      const option = document.createElement("option");
1660      option.value = site.host; option.textContent = site.name || site.host; return option;
1661    }));
1662    picker.value = result.selectedHost;
1663    picker.onchange = () => loadCustomerSite(picker.value).catch(showDashboardError);
1664    if (result.requestedSiteRejected) {
1665      const page = new URL(window.location.href);
1666      page.searchParams.set("site", result.selectedHost);
1667      window.history.replaceState({}, "", page);
1668    }
1669    return renderCustomerBundle(result.bundle);
1670  };
1671  const showDashboardError = (error) => {
1672    if (error?.code === "dashboard_request_superseded") return;
1673    ++dashboardGeneration;
1674    clearCustomerView();
1675    const loading = document.querySelector("[data-dashboard-loading]");
1676    if (!loading) return;
1677    const expired = error?.status === 401 || ["dashboard_session_invalid_or_expired", "HTTP 401"].some((value) => String(error.message).includes(value));
1678    if (expired) {
1679      setDashboardMode("login_required");
1680      return;
1681    }
1682    loading.removeAttribute("hidden");
1683    loading.textContent = `${localized("Unable to load evidence", "資料載入失敗")}: ${error.message}`;
1684  };
1685  loadDashboard().catch(showDashboardError);
1686  window.addEventListener("pagehide", () => { ++dashboardGeneration; clearCustomerView(); });
1687  window.addEventListener("pageshow", (event) => {
1688    if (event.persisted) loadDashboard().catch(showDashboardError);
1689  });
1690
1691  document.querySelector("[data-dashboard-logout]")?.addEventListener("click", async () => {
1692    ++dashboardGeneration;
1693    clearCustomerView();
1694    try { await dashboardClient?.logout(); } finally { window.location.replace("/dashboard?logout=1"); }
1695  });
1696
1697  document.querySelector("[data-history-form]")?.addEventListener("submit", (event) => {
1698    event.preventDefault();
1699    loadHistory(activeDashboardHost).catch(showDashboardError);
1700  });
1701  document.querySelectorAll("[data-export]").forEach((button) => button.addEventListener("click", () => {
1702    const format = button.dataset.export;
1703    if (!publicDemoDashboard) {
1704      window.location.href = `/api/dashboard/export?${historyQuery(activeDashboardHost,format)}`;
1705      return;
1706    }
1707    const body = format === "json" ? JSON.stringify(dashboardHistory,null,2) : [
1708      "snapshot_id,observed_at,seo,aeo,geo,ago",
1709      ...(dashboardHistory?.snapshots || []).map((row) => [row.snapshot_id,row.observed_at,...["seo","aeo","geo","ago"].map((axis) => metricValue(row,"scores",axis) ?? "")].join(",")),
1710    ].join("\n");
1711    const blob = new Blob([body],{type:format === "json" ? "application/json" : "text/csv"});
1712    const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = `twin3-agent-id-report.${format}`; link.click(); URL.revokeObjectURL(link.href);
1713  }));
1714  document.querySelector("[data-print-report]")?.addEventListener("click", () => window.print());
1715
1716  const setupDialog = document.querySelector("[data-connector-dialog]");
1717  const setupCatalog = {
1718    a1: { title:"AI 爬蟲讀取紀錄 / AI crawler reads", copy:"讓 Cloudflare 每 15 分鐘把已去除查詢參數的 AI 爬蟲統計送回母體。", requirements:["Cloudflare Zone ID","唯讀 Analytics Token","Site Agent Key"], command:(host)=>`SITE_AGENT_KEY=... CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ZONE_ID=... npx --package @twin3-ai/agent-id agent-id-cloudflare-a1 --url https://${host} --once` },
1719    a2: { title:"AI 引用測試 / AI citation probes", copy:"固定採購問題與模型,才能比較每月是否真的被提及與引用。", requirements:["採購問題主題","要測試的 AI 引擎","Site Agent Key"], command:(host)=>`npx --package @twin3-ai/agent-id agent-id a2-probe-set https://${host} --topic "your buyer question" --engine ChatGPT,Gemini,Claude` },
1720    b1: { title:"GA4 或訂單歸因 / Conversion attribution", copy:"GA4 顯示 AI 來源關鍵事件;CRM/訂單匯出才確認詢價與成交。", requirements:["GA4 Property ID 或 CRM JSON 匯出","唯讀 OAuth Token","Site Agent Key"], command:(host)=>`SITE_AGENT_KEY=... GA4_PROPERTY_ID=... GA4_ACCESS_TOKEN=... npx --package @twin3-ai/agent-id agent-id-b1-sync --ga4 --url https://${host}` }
1721  };
1722  document.querySelectorAll("[data-connector-setup]").forEach((button)=>button.addEventListener("click",()=>{
1723    const item=setupCatalog[button.dataset.connectorSetup]; if(!item||!setupDialog)return;
1724    setupDialog.querySelector("[data-setup-title]").textContent=item.title;
1725    setupDialog.querySelector("[data-setup-copy]").textContent=item.copy;
1726    setupDialog.querySelector("[data-setup-requirements]").innerHTML=item.requirements.map((value,index)=>`<div>${index+1}. ${value}</div>`).join("");
1727    setupDialog.querySelector("[data-setup-command]").textContent=item.command(activeDashboardHost || URL_PLACEHOLDER);
1728    setupDialog.showModal();
1729  }));
1730  document.querySelector("[data-copy-setup]")?.addEventListener("click",async()=>{const command=document.querySelector("[data-setup-command]")?.textContent||"";await navigator.clipboard.writeText(command);});
1731  document.querySelector("[data-check-setup]")?.addEventListener("click",()=>loadDashboard().catch(showDashboardError));
1732
1733  const auditForm = document.querySelector("[data-audit-form]");
1734  auditForm?.addEventListener("submit", async (event) => {
1735    event.preventDefault();
1736    const epoch = dashboardGeneration;
1737    const target = normalizeUrl(document.getElementById("dashboard-audit-url")?.value || "");
1738    const progress = document.querySelector("[data-audit-progress]");
1739    const progressText = document.querySelector("[data-audit-progress-text]");
1740    const result = document.querySelector("[data-audit-quick-result]");
1741    progress?.removeAttribute("hidden"); result?.setAttribute("hidden", "");
1742    if (progressText) progressText.textContent = localized("Reading public pages, structured data, and Agent files…", "正在讀取公開頁面、結構化資料與 Agent 設定檔…");
1743    try {
1744      const response = await fetch(`/api/agent/audit_report?url=${encodeURIComponent(target)}`, {headers:{Accept:"application/json"}});
1745      const payload = await response.json();
1746      if (epoch !== dashboardGeneration) return;
1747      const report = payload.report || payload;
1748      if (!response.ok || payload.ok === false) throw new Error(payload.error || `HTTP ${response.status}`);
1749      if (measurementFailed(report)) {
1750        result.textContent = `${target} · ${measurementFailureCopy(report)}`;
1751        result.removeAttribute("hidden");
1752        return;
1753      }
1754      const axes = report.scorecard || {};
1755      result.textContent = `${target} · ${report.overall_score ?? "—"}/100 · SEO ${axes.seo?.score ?? "—"} · AEO ${axes.aeo?.score ?? "—"} · GEO ${axes.geo?.score ?? "—"} · AGO ${axes.ago?.score ?? "—"}`;
1756      result.removeAttribute("hidden");
1757    } catch (error) {
1758      if (epoch !== dashboardGeneration) return;
1759      result.textContent = `${localized("Audit failed", "檢測失敗")}: ${error.message}`;
1760      result.removeAttribute("hidden");
1761    } finally { if (epoch === dashboardGeneration) progress?.setAttribute("hidden", ""); }
1762  });
1763})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.