UNPKG

@xuda.io/runtime-bundle

Version:

The Xuda Runtime Bundle refers to a collection of scripts and libraries packaged together to provide the necessary runtime environment for executing plugins or components in the Xuda platform.

443 lines (414 loc) 16.4 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> <title>__NAME__ · Xuda</title> <meta name="description" content="__BIO__" /> <!-- Open Graph / link previews --> <meta property="og:type" content="profile" /> <meta property="og:title" content="__NAME__ · Xuda" /> <meta property="og:description" content="__BIO__" /> <meta property="og:image" content="__AVATAR__" /> <meta property="og:url" content="__PAGE_URL__" /> <meta name="twitter:card" content="summary" /> <meta name="twitter:title" content="__NAME__ · Xuda" /> <meta name="twitter:description" content="__BIO__" /> <meta name="twitter:image" content="__AVATAR__" /> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> <script src="https://accounts.google.com/gsi/client" async defer></script> <style> :root { --xu-navy: #1a3557; --xu-navy-2: #25406a; --xu-accent: #4f8cff; --xu-ink: #16233a; --xu-muted: #6b7a90; --xu-line: #e7ecf3; --xu-bg: #f4f7fb; --xu-card: #ffffff; --xu-radius: 22px; --xu-shadow: 0 18px 50px rgba(26, 53, 87, 0.14); } * { box-sizing: border-box; } html, body { margin: 0; padding: 0; } body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; color: var(--xu-ink); background: radial-gradient(1200px 600px at 50% -200px, #e9f1ff 0%, var(--xu-bg) 55%); min-height: 100vh; display: flex; align-items: flex-start; justify-content: center; padding: 48px 18px 64px; -webkit-font-smoothing: antialiased; } .xu-card { width: 100%; max-width: 560px; background: var(--xu-card); border-radius: var(--xu-radius); box-shadow: var(--xu-shadow); overflow: hidden; border: 1px solid var(--xu-line); } .xu-cover { height: 132px; background: linear-gradient(120deg, var(--xu-navy) 0%, var(--xu-navy-2) 55%, var(--xu-accent) 140%); position: relative; } .xu-cover::after { content: ''; position: absolute; inset: 0; background: radial-gradient(420px 200px at 80% -40%, rgba(255, 255, 255, 0.22), transparent 70%); } .xu-body { padding: 0 28px 30px; position: relative; z-index: 1; } .xu-avatar-wrap { margin-top: -64px; display: flex; justify-content: center; position: relative; /* lift above the (positioned) cover so the banner doesn't paint over the avatar / hide the face */ z-index: 2; } .xu-avatar img { object-position: center 28%; } .xu-avatar { width: 128px; height: 128px; border-radius: 50%; object-fit: cover; background: #dfe7f2; border: 5px solid #fff; box-shadow: 0 10px 26px rgba(26, 53, 87, 0.22); display: flex; align-items: center; justify-content: center; font-size: 46px; font-weight: 700; color: var(--xu-navy); overflow: hidden; } .xu-name { text-align: center; font-size: 26px; font-weight: 800; letter-spacing: -0.01em; margin: 16px 0 2px; } .xu-handle { text-align: center; color: var(--xu-accent); font-weight: 600; font-size: 14px; margin: 2px 0 0; } .xu-business { text-align: center; color: var(--xu-muted); font-weight: 600; font-size: 15px; margin: 2px 0; } .xu-meta { display: flex; flex-wrap: wrap; gap: 8px 18px; justify-content: center; align-items: center; color: var(--xu-muted); font-size: 14px; margin: 10px 0 4px; } .xu-meta a { color: var(--xu-accent); text-decoration: none; font-weight: 600; } .xu-meta a:hover { text-decoration: underline; text-underline-offset: 3px; } .xu-flag { font-size: 16px; line-height: 1; } .xu-bio { margin: 18px auto 4px; max-width: 460px; text-align: center; color: #36475f; font-size: 15.5px; line-height: 1.6; white-space: pre-wrap; } .xu-divider { height: 1px; background: var(--xu-line); margin: 24px 0 22px; } .xu-cta { text-align: center; } .xu-cta-title { font-size: 14px; color: var(--xu-muted); margin-bottom: 14px; font-weight: 500; } .xu-ask-btn { appearance: none; border: none; cursor: pointer; width: 100%; max-width: 320px; margin: 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 10px; padding: 14px 22px; border-radius: 14px; font-size: 16px; font-weight: 700; color: #fff; background: linear-gradient(120deg, var(--xu-navy) 0%, var(--xu-accent) 130%); box-shadow: 0 12px 26px rgba(79, 140, 255, 0.32); transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease; } .xu-ask-btn:hover { transform: translateY(-1px); box-shadow: 0 16px 32px rgba(79, 140, 255, 0.4); } .xu-ask-btn:active { transform: translateY(0); } .xu-google-wrap { display: flex; justify-content: center; margin-top: 6px; } .xu-hint { font-size: 12.5px; color: var(--xu-muted); margin-top: 12px; } .xu-error { color: #c0392b; font-size: 13px; margin-top: 12px; min-height: 16px; } .xu-result { display: none; margin: 4px auto 0; max-width: 360px; padding: 16px 18px; border-radius: 14px; font-size: 15px; line-height: 1.5; background: #eef5ff; color: var(--xu-navy); border: 1px solid #d6e6ff; } .xu-result.xu-open { background: #eafaf0; color: #1c7c45; border-color: #cdeedd; } .xu-result .xu-result-emoji { font-size: 22px; display: block; margin-bottom: 6px; } .xu-spinner { width: 18px; height: 18px; border: 2.5px solid rgba(255, 255, 255, 0.45); border-top-color: #fff; border-radius: 50%; animation: xu-spin 0.7s linear infinite; } @keyframes xu-spin { to { transform: rotate(360deg); } } .xu-footer { text-align: center; margin-top: 22px; font-size: 12px; color: var(--xu-muted); } .xu-footer a { color: var(--xu-muted); text-decoration: none; font-weight: 600; } .xu-footer a:hover { text-decoration: underline; } [hidden] { display: none !important; } @media (max-width: 480px) { body { padding: 20px 12px 40px; } .xu-name { font-size: 23px; } } </style> </head> <body> <main class="xu-card" role="main" data-target-uid="__TARGET_UID__" data-avatar="__AVATAR__"> <div class="xu-cover"></div> <div class="xu-body"> <div class="xu-avatar-wrap"> <div class="xu-avatar" id="xu-avatar"></div> </div> <h1 class="xu-name" id="xu-name">__NAME__</h1> <div class="xu-handle" id="xu-handle" hidden>@<span id="xu-handle-val">__USERNAME__</span></div> <div class="xu-business" id="xu-business">__BUSINESS_NAME__</div> <div class="xu-meta"> <span id="xu-location-wrap" hidden><span class="xu-flag" id="xu-flag">__FLAG__</span> <span id="xu-location">__LOCATION__</span></span> <a id="xu-website" href="__WEBSITE_URL__" target="_blank" rel="noopener nofollow" hidden>__WEBSITE_LABEL__</a> </div> <p class="xu-bio" id="xu-bio">__BIO__</p> <div class="xu-divider"></div> <section class="xu-cta"> <div class="xu-cta-title">Connect with <span id="xu-first-name">this member</span> on Xuda</div> <button type="button" class="xu-ask-btn" id="xu-ask-btn"> <span id="xu-ask-label">Ask friendship</span> </button> <div class="xu-google-wrap" id="xu-google-wrap" hidden></div> <div class="xu-result" id="xu-result"></div> <div class="xu-error" id="xu-error"></div> <div class="xu-hint" id="xu-hint">You'll be asked to sign in with Google to send your request.</div> </section> <div class="xu-footer">Powered by <a href="https://xuda.ai" target="_blank" rel="noopener">Xuda</a></div> </div> </main> <script> (function(){ 'use strict'; // CPI_BASE and GOOGLE_CLIENT_ID are server-sanitized to safe charsets (no // quotes), so they're safe in JS string literals. TARGET_UID and the name // are HTML-entity-encoded by the server, so read them from the DOM (decoded) // rather than injecting into JS. var CPI_BASE = '__CPI_BASE__'; var GOOGLE_CLIENT_ID = '__GOOGLE_CLIENT_ID__'; var $root = document.querySelector('main.xu-card'); var $name = document.getElementById('xu-name'); var $avatar = document.getElementById('xu-avatar'); var $business = document.getElementById('xu-business'); var $locationWrap = document.getElementById('xu-location-wrap'); var $location = document.getElementById('xu-location'); var $flag = document.getElementById('xu-flag'); var $website = document.getElementById('xu-website'); var $bio = document.getElementById('xu-bio'); var $firstName = document.getElementById('xu-first-name'); var $askBtn = document.getElementById('xu-ask-btn'); var $askLabel = document.getElementById('xu-ask-label'); var $googleWrap = document.getElementById('xu-google-wrap'); var $result = document.getElementById('xu-result'); var $error = document.getElementById('xu-error'); var $hint = document.getElementById('xu-hint'); function txt(el){ return (el && el.textContent || '').trim(); } // Values that may contain special chars are read from the DOM (already // entity-decoded by the parser), never injected into JS string literals. var TARGET_UID = ($root && $root.getAttribute('data-target-uid')) || ''; var FULL_NAME = txt($name) || 'Xuda Member'; // Avatar: use the photo if present, otherwise fall back to initials. var avatarUrl = ($root && $root.getAttribute('data-avatar')) || ''; if (avatarUrl) { var img = document.createElement('img'); img.alt = FULL_NAME; img.referrerPolicy = 'no-referrer'; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'cover'; img.onerror = function(){ renderInitials(); }; img.src = avatarUrl; $avatar.appendChild(img); } else { renderInitials(); } function renderInitials(){ $avatar.textContent = ''; var parts = FULL_NAME.split(/\s+/).filter(Boolean); var initials = (parts[0] ? parts[0][0] : '') + (parts[1] ? parts[1][0] : ''); $avatar.textContent = (initials || 'X').toUpperCase(); } // Hide empty optional blocks. var $handle = document.getElementById('xu-handle'); var $handleVal = document.getElementById('xu-handle-val'); if ($handle) $handle.hidden = !txt($handleVal); if (!txt($business)) $business.hidden = true; if (txt($location)) { $locationWrap.hidden = false; if (!txt($flag)) $flag.hidden = true; } if (txt($website) && $website.getAttribute('href')) $website.hidden = false; if (!txt($bio)) $bio.hidden = true; var firstName = (FULL_NAME.split(/\s+/)[0]) || ''; if (firstName) $firstName.textContent = firstName; function cpi(method, body){ return fetch(CPI_BASE + method, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}), }).then(function(r){ return r.json(); }); } function showResult(state){ $googleWrap.hidden = true; $askBtn.hidden = true; $hint.hidden = true; $error.textContent = ''; $result.style.display = 'block'; if (state === 'open') { $result.className = 'xu-result xu-open'; $result.innerHTML = '<span class="xu-result-emoji">🎉</span>You’re now connected with ' + escapeHtml(firstName || FULL_NAME) + '!'; } else if (state === 'declined') { $result.className = 'xu-result'; $result.innerHTML = '<span class="xu-result-emoji">✉️</span>This request can’t be completed right now.'; } else { $result.className = 'xu-result'; $result.innerHTML = '<span class="xu-result-emoji">✉️</span>Friend request sent to ' + escapeHtml(firstName || FULL_NAME) + '. You’ll be connected once they accept.'; } } function escapeHtml(s){ return String(s || '').replace(/[&<>"']/g, function(c){ return ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' })[c]; }); } function onGoogleCredential(resp){ if (!resp || !resp.credential) return; $error.textContent = ''; setLoading(true); cpi('public_profile_ask_friendship', { id_token: resp.credential, target_uid: TARGET_UID, }).then(function(r){ setLoading(false); if (!r || r.code < 0) { $error.textContent = (r && r.data) ? friendlyError(r.data) : 'Something went wrong. Please try again.'; return; } showResult((r.data && r.data.state) || 'pending'); }).catch(function(){ setLoading(false); $error.textContent = 'Network error. Please try again.'; }); } function friendlyError(code){ if (code === 'cannot_friend_self') return 'You can’t send a friend request to yourself.'; if (code === 'target_unavailable' || code === 'target_not_found') return 'This profile is no longer available.'; if (/google/.test(String(code))) return 'Google sign-in failed. Please try again.'; return 'Something went wrong. Please try again.'; } function setLoading(on){ if (on) { $askBtn.hidden = false; $googleWrap.hidden = true; $askLabel.textContent = ''; var sp = document.createElement('span'); sp.className = 'xu-spinner'; $askBtn.innerHTML = ''; $askBtn.appendChild(sp); $askBtn.disabled = true; } else { $askBtn.disabled = false; $askBtn.innerHTML = '<span id="xu-ask-label">Ask friendship</span>'; } } // The big button reveals Google's official sign-in button (so the credential // flow is genuine Google UI). Clicking "Ask friendship" swaps to it. var googleReady = false; function initGoogle(){ if (!GOOGLE_CLIENT_ID || !window.google || !window.google.accounts || !window.google.accounts.id) return false; try { window.google.accounts.id.initialize({ client_id: GOOGLE_CLIENT_ID, callback: onGoogleCredential, ux_mode: 'popup', auto_select: false, }); googleReady = true; return true; } catch (e) { return false; } } function revealGoogleButton(){ $askBtn.hidden = true; $hint.hidden = true; $googleWrap.hidden = false; $googleWrap.innerHTML = ''; try { window.google.accounts.id.renderButton($googleWrap, { theme: 'filled_blue', size: 'large', type: 'standard', shape: 'pill', text: 'continue_with', logo_alignment: 'left', width: 300, }); } catch (e) { $error.textContent = 'Sign-in is temporarily unavailable. Please try again shortly.'; $askBtn.hidden = false; } // If GIS silently fails to render (origin error), restore the button. setTimeout(function(){ if ($googleWrap && !$googleWrap.firstElementChild) { $googleWrap.hidden = true; $askBtn.hidden = false; $error.textContent = 'Sign-in is temporarily unavailable. Please try again shortly.'; } }, 1500); } $askBtn.addEventListener('click', function(){ if (!googleReady && !initGoogle()) { $error.textContent = 'Sign-in is loading, please try again in a moment.'; return; } revealGoogleButton(); }); // Warm up GIS as soon as it's available. (function waitForGsi(t){ if (initGoogle()) return; if (t > 25) return; setTimeout(function(){ waitForGsi(t + 1); }, 200); })(0); })(); </script> </body> </html>