@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
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 ; }
@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 ({ '&':'&','<':'<','>':'>','"':'"',"'":''' })[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>