cp空间
BIN
activity/cp-space/assets/parts/cp-lv1-avatar@2x.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
activity/cp-space/assets/parts/cp-lv1-bg@2x.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
activity/cp-space/assets/parts/cp-lv1-button@2x.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
activity/cp-space/assets/parts/cp-lv1-heart@2x.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
activity/cp-space/assets/parts/cp-lv1-level-pill@2x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
activity/cp-space/assets/parts/cp-lv1-xp-fill@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
activity/cp-space/assets/parts/cp-lv1-xp-track@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
activity/cp-space/assets/parts/cp-lv2-avatar@2x.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
activity/cp-space/assets/parts/cp-lv2-bg@2x.png
Normal file
|
After Width: | Height: | Size: 358 KiB |
BIN
activity/cp-space/assets/parts/cp-lv2-button@2x.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
activity/cp-space/assets/parts/cp-lv2-heart@2x.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
activity/cp-space/assets/parts/cp-lv2-level-pill@2x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
activity/cp-space/assets/parts/cp-lv2-xp-fill@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
activity/cp-space/assets/parts/cp-lv2-xp-track@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
activity/cp-space/assets/parts/cp-lv3-avatar@2x.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
activity/cp-space/assets/parts/cp-lv3-bg@2x.png
Normal file
|
After Width: | Height: | Size: 439 KiB |
BIN
activity/cp-space/assets/parts/cp-lv3-button@2x.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
activity/cp-space/assets/parts/cp-lv3-heart@2x.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
activity/cp-space/assets/parts/cp-lv3-level-pill@2x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
activity/cp-space/assets/parts/cp-lv3-xp-fill@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
activity/cp-space/assets/parts/cp-lv3-xp-track@2x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
activity/cp-space/assets/parts/cp-lv4-avatar@2x.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
activity/cp-space/assets/parts/cp-lv4-bg@2x.png
Normal file
|
After Width: | Height: | Size: 405 KiB |
BIN
activity/cp-space/assets/parts/cp-lv4-button@2x.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
activity/cp-space/assets/parts/cp-lv4-heart@2x.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
activity/cp-space/assets/parts/cp-lv4-level-pill@2x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
activity/cp-space/assets/parts/cp-lv4-xp-fill@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
activity/cp-space/assets/parts/cp-lv4-xp-track@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
activity/cp-space/assets/parts/cp-lv5-avatar@2x.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
activity/cp-space/assets/parts/cp-lv5-bg@2x.png
Normal file
|
After Width: | Height: | Size: 367 KiB |
BIN
activity/cp-space/assets/parts/cp-lv5-button@2x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
activity/cp-space/assets/parts/cp-lv5-heart@2x.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
activity/cp-space/assets/parts/cp-lv5-level-pill@2x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
activity/cp-space/assets/parts/cp-lv5-xp-fill@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
activity/cp-space/assets/parts/cp-lv5-xp-track@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@ -1,46 +0,0 @@
|
||||
<svg width="375" height="812" viewBox="0 0 375 812" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="375" height="812" fill="#26084E"/>
|
||||
<rect width="375" height="812" fill="url(#bgGlow)"/>
|
||||
<path d="M236 -12C229 38 244 78 287 111C328 143 365 161 393 212" stroke="#8C43D2" stroke-opacity=".18" stroke-width="2"/>
|
||||
<path d="M251 -8C257 33 278 63 315 80C351 96 377 122 392 159" stroke="#A35AE7" stroke-opacity=".16" stroke-width="1.4"/>
|
||||
<path d="M67 830C73 760 52 707 4 671C-35 642 -58 599 -61 542" stroke="#9C4DDA" stroke-opacity=".2" stroke-width="2"/>
|
||||
<path d="M34 812C26 757 4 720 -31 699C-61 680 -75 647 -72 599" stroke="#B65BEE" stroke-opacity=".16" stroke-width="1.4"/>
|
||||
<g opacity=".42" stroke="#A55BE9" stroke-width="1.1">
|
||||
<path d="M296 11c29-9 57 9 60 39 2 23-15 43-38 45 12-12 13-29 3-40-11-13-31-11-41 3"/>
|
||||
<path d="M323 13c17 11 20 35 6 49"/>
|
||||
<path d="M351 46c-8-14-24-21-40-16"/>
|
||||
<path d="M18 716c31-12 63 8 66 41 3 26-17 49-43 51 14-14 15-33 4-46-13-15-36-13-49 4"/>
|
||||
<path d="M47 716c19 13 23 40 7 56"/>
|
||||
<path d="M80 755c-10-16-29-24-47-18"/>
|
||||
<path d="M325 728c24-4 45 12 47 36 2 19-12 36-31 38 10-10 10-23 2-32-9-10-25-9-33 3"/>
|
||||
</g>
|
||||
<g opacity=".95">
|
||||
<circle cx="18" cy="401" r="1.3" fill="#FFFFFF"/>
|
||||
<circle cx="61" cy="659" r="1.9" fill="#FFFFFF"/>
|
||||
<circle cx="133" cy="512" r="1.2" fill="#DFB8FF"/>
|
||||
<circle cx="187" cy="710" r="1.6" fill="#FFFFFF"/>
|
||||
<circle cx="216" cy="438" r="1.3" fill="#FFFFFF"/>
|
||||
<circle cx="268" cy="596" r="1.5" fill="#CFA4FF"/>
|
||||
<circle cx="327" cy="505" r="1.9" fill="#FFFFFF"/>
|
||||
<circle cx="356" cy="420" r="1.3" fill="#E8D1FF"/>
|
||||
<circle cx="334" cy="735" r="1.7" fill="#FFFFFF"/>
|
||||
<circle cx="7" cy="218" r="1.2" fill="#FFFFFF"/>
|
||||
<circle cx="242" cy="133" r="1.4" fill="#FFFFFF"/>
|
||||
<circle cx="318" cy="76" r="1.2" fill="#DAB5FF"/>
|
||||
<circle cx="36" cy="185" r="1.6" fill="#DDBDFF"/>
|
||||
<circle cx="184" cy="83" r="1.3" fill="#FFFFFF"/>
|
||||
</g>
|
||||
<g opacity=".72">
|
||||
<path d="M8 430l2.2 5.2 5.3 2.2-5.3 2.1L8 445l-2.2-5.5-5.3-2.1 5.3-2.2L8 430Z" fill="#FFFFFF"/>
|
||||
<path d="M341 497l2.8 6.6 6.6 2.8-6.6 2.7-2.8 6.7-2.7-6.7-6.7-2.7 6.7-2.8 2.7-6.6Z" fill="#FFFFFF"/>
|
||||
<path d="M204 641l2 4.8 4.8 2-4.8 2-2 4.8-2-4.8-4.8-2 4.8-2 2-4.8Z" fill="#F2E3FF"/>
|
||||
<path d="M356 704l1.9 4.4 4.4 1.9-4.4 1.8-1.9 4.5-1.8-4.5-4.5-1.8 4.5-1.9 1.8-4.4Z" fill="#DAB6FF"/>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient id="bgGlow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(309 52) rotate(116) scale(227 143)">
|
||||
<stop stop-color="#6E239D" stop-opacity=".62"/>
|
||||
<stop offset=".44" stop-color="#3A0E68" stop-opacity=".82"/>
|
||||
<stop offset="1" stop-color="#230747"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
BIN
activity/cp-space/assets/space-bg@4x.png
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
@ -10,9 +10,9 @@
|
||||
<link rel="icon" href="data:," />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="../../common/theme.css?v=20260618-cp-space"
|
||||
href="../../common/theme.css?v=20260618-cp-space-parts"
|
||||
/>
|
||||
<link rel="stylesheet" href="./style.css?v=20260618-cp-space" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260618-cp-space-parts" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
@ -68,10 +68,10 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="../../common/i18n.js?v=20260618-cp-space"></script>
|
||||
<script src="../../common/api.js?v=20260618-cp-space"></script>
|
||||
<script src="../../common/jsbridge.js?v=20260618-cp-space"></script>
|
||||
<script src="../../common/toast.js?v=20260618-cp-space"></script>
|
||||
<script src="./script.js?v=20260618-cp-space"></script>
|
||||
<script src="../../common/i18n.js?v=20260618-cp-space-parts"></script>
|
||||
<script src="../../common/api.js?v=20260618-cp-space-parts"></script>
|
||||
<script src="../../common/jsbridge.js?v=20260618-cp-space-parts"></script>
|
||||
<script src="../../common/toast.js?v=20260618-cp-space-parts"></script>
|
||||
<script src="./script.js?v=20260618-cp-space-parts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,12 +1,24 @@
|
||||
(function () {
|
||||
var VALID_TABS = ['cp', 'brother', 'sister'];
|
||||
var LEVEL_IMAGES = {
|
||||
1: './assets/level-1@4x.png',
|
||||
2: './assets/level-2@4x.png',
|
||||
3: './assets/level-3@4x.png',
|
||||
4: './assets/level-4@4x.png',
|
||||
5: './assets/level-5@4x.png',
|
||||
};
|
||||
var PARTS_BASE = './assets/parts/';
|
||||
var PART_NAMES = [
|
||||
'bg',
|
||||
'avatar',
|
||||
'heart',
|
||||
'button',
|
||||
'xp-track',
|
||||
'xp-fill',
|
||||
'level-pill',
|
||||
];
|
||||
var LEVEL_PARTS = {};
|
||||
|
||||
for (var partLevel = 1; partLevel <= 5; partLevel += 1) {
|
||||
LEVEL_PARTS[partLevel] = {};
|
||||
PART_NAMES.forEach(function (partName) {
|
||||
LEVEL_PARTS[partLevel][partName] =
|
||||
PARTS_BASE + 'cp-lv' + partLevel + '-' + partName + '@2x.png';
|
||||
});
|
||||
}
|
||||
|
||||
var page = document.getElementById('page');
|
||||
var contentPanel = document.getElementById('contentPanel');
|
||||
@ -17,13 +29,24 @@
|
||||
|
||||
var state = {
|
||||
activeTab: normalizeTab(readParam('tab')) || 'cp',
|
||||
requestSeq: 0,
|
||||
cancelingId: '',
|
||||
tabs: {
|
||||
cp: [normalizeLevel(readParam('level')) || 1],
|
||||
brother: [2, 2],
|
||||
sister: [4, 5],
|
||||
cp: createTabState(),
|
||||
brother: createTabState(),
|
||||
sister: createTabState(),
|
||||
},
|
||||
};
|
||||
|
||||
function createTabState() {
|
||||
return {
|
||||
loading: false,
|
||||
loaded: false,
|
||||
error: '',
|
||||
relationships: [],
|
||||
};
|
||||
}
|
||||
|
||||
function readParam(name) {
|
||||
var params = new URLSearchParams(window.location.search || '');
|
||||
return params.get(name);
|
||||
@ -40,7 +63,16 @@
|
||||
var level = Number(value);
|
||||
return Number.isFinite(level) && level >= 1 && level <= 5
|
||||
? Math.floor(level)
|
||||
: 0;
|
||||
: 1;
|
||||
}
|
||||
|
||||
function readNumber(value) {
|
||||
var number = Number(value);
|
||||
return Number.isFinite(number) ? number : 0;
|
||||
}
|
||||
|
||||
function readString(value) {
|
||||
return String(value == null ? '' : value).trim();
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
@ -59,6 +91,123 @@
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
if (window.HyAppToast && window.HyAppToast.show) {
|
||||
window.HyAppToast.show(message);
|
||||
return;
|
||||
}
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('hyapp:toast', { detail: { message: message } })
|
||||
);
|
||||
}
|
||||
|
||||
function profileFromData(data) {
|
||||
var json = data || {};
|
||||
return {
|
||||
userId: readString(json.user_id || json.id),
|
||||
displayUserId: readString(json.display_user_id),
|
||||
username: readString(json.username || json.nickname || json.name),
|
||||
avatar: readString(
|
||||
json.avatar ||
|
||||
json.avatar_url ||
|
||||
json.avatar_path ||
|
||||
(json.profile && json.profile.avatar)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function displayName(profile) {
|
||||
return (
|
||||
readString(profile.username) ||
|
||||
readString(profile.displayUserId) ||
|
||||
readString(profile.userId) ||
|
||||
t('cpSpace.unknownUser', 'User')
|
||||
);
|
||||
}
|
||||
|
||||
function daysFromFormedAt(formedAtMs) {
|
||||
var formedAt = readNumber(formedAtMs);
|
||||
if (formedAt <= 0) return 0;
|
||||
var now = Date.now();
|
||||
if (now <= formedAt) return 1;
|
||||
return Math.floor((now - formedAt) / 86400000) + 1;
|
||||
}
|
||||
|
||||
function currentLevelExp(item) {
|
||||
var total = Math.max(0, readNumber(item.intimacy_value));
|
||||
var currentThreshold = readNumber(item.current_level_threshold);
|
||||
if (currentThreshold <= 0 || currentThreshold >= total) return total;
|
||||
return total - currentThreshold;
|
||||
}
|
||||
|
||||
function nextLevelExp(item) {
|
||||
if (item.max_level) return 0;
|
||||
var nextThreshold = readNumber(item.next_level_threshold);
|
||||
var currentThreshold = readNumber(item.current_level_threshold);
|
||||
if (nextThreshold > 0) {
|
||||
if (currentThreshold > 0 && nextThreshold > currentThreshold) {
|
||||
return nextThreshold - currentThreshold;
|
||||
}
|
||||
return nextThreshold;
|
||||
}
|
||||
var total = Math.max(0, readNumber(item.intimacy_value));
|
||||
var remaining = readNumber(item.needed_for_next_level);
|
||||
return remaining > 0 ? total + remaining : 0;
|
||||
}
|
||||
|
||||
function progressPercent(item) {
|
||||
if (item.max_level) return 100;
|
||||
var explicit = readNumber(item.level_progress_percent);
|
||||
if (explicit > 0) return Math.max(0, Math.min(100, explicit));
|
||||
var next = nextLevelExp(item);
|
||||
if (next <= 0) return 0;
|
||||
return Math.max(0, Math.min(100, (currentLevelExp(item) / next) * 100));
|
||||
}
|
||||
|
||||
function normalizeRelationship(item, tab) {
|
||||
var relationType = normalizeTab(item && item.relation_type) || tab;
|
||||
var level = normalizeLevel(item && item.level);
|
||||
var me = profileFromData(item && item.me);
|
||||
var partner = profileFromData(item && item.partner);
|
||||
var currentExp = currentLevelExp(item || {});
|
||||
var nextExp = nextLevelExp(item || {});
|
||||
return {
|
||||
relationshipId: readString(item && item.relationship_id),
|
||||
relationType: relationType,
|
||||
status: readString(item && item.status) || 'active',
|
||||
level: level,
|
||||
me: me,
|
||||
partner: partner,
|
||||
days: daysFromFormedAt(item && item.formed_at_ms),
|
||||
intimacyValue: Math.max(0, readNumber(item && item.intimacy_value)),
|
||||
currentExp: currentExp,
|
||||
nextExp: nextExp,
|
||||
progressPercent: progressPercent(item || {}),
|
||||
maxLevel: Boolean(item && item.max_level),
|
||||
breakupCostCoins: Math.max(
|
||||
0,
|
||||
readNumber(item && item.breakup_cost_coins)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function activeRelationships(data, tab) {
|
||||
var list = Array.isArray(data && data.relationships)
|
||||
? data.relationships
|
||||
: [];
|
||||
return list
|
||||
.map(function (item) {
|
||||
return normalizeRelationship(item || {}, tab);
|
||||
})
|
||||
.filter(function (item) {
|
||||
return (
|
||||
item.relationshipId &&
|
||||
item.relationType === tab &&
|
||||
item.status === 'active'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function setActiveTab(tab) {
|
||||
var nextTab = normalizeTab(tab) || 'cp';
|
||||
state.activeTab = nextTab;
|
||||
@ -70,36 +219,184 @@
|
||||
);
|
||||
});
|
||||
render();
|
||||
if (!state.tabs[nextTab].loaded && !state.tabs[nextTab].loading) {
|
||||
loadTab(nextTab);
|
||||
}
|
||||
}
|
||||
|
||||
function cardHTML(level, index) {
|
||||
var safeLevel = normalizeLevel(level) || 1;
|
||||
function visualLevel(item) {
|
||||
return item.relationType === 'cp' ? item.level : 1;
|
||||
}
|
||||
|
||||
function partsFor(item) {
|
||||
return LEVEL_PARTS[visualLevel(item)] || LEVEL_PARTS[1];
|
||||
}
|
||||
|
||||
function nextLevelLabel(item) {
|
||||
if (item.maxLevel) return 'Max';
|
||||
return t('cpSpace.levelValue', 'Lv.{level}').replace(
|
||||
'{level}',
|
||||
String(Math.min(5, item.level + 1))
|
||||
);
|
||||
}
|
||||
|
||||
function expLabel(item) {
|
||||
if (item.maxLevel || item.nextExp <= 0) {
|
||||
return t('cpSpace.maxLevelExp', '{exp} EXP').replace(
|
||||
'{exp}',
|
||||
formatNumber(item.intimacyValue)
|
||||
);
|
||||
}
|
||||
return t('cpSpace.expProgress', '{current}/{next} EXP')
|
||||
.replace('{current}', formatNumber(item.currentExp))
|
||||
.replace('{next}', formatNumber(item.nextExp));
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
var number = Math.max(0, Math.floor(readNumber(value)));
|
||||
return String(number).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
function avatarHTML(profile, side, frameSrc) {
|
||||
var name = displayName(profile);
|
||||
var avatar = readString(profile.avatar);
|
||||
return [
|
||||
'<figure class="relation-avatar relation-avatar--',
|
||||
side,
|
||||
'">',
|
||||
avatar
|
||||
? [
|
||||
'<img class="relation-avatar-photo" src="',
|
||||
escapeHTML(avatar),
|
||||
'" alt="',
|
||||
escapeHTML(name),
|
||||
'" />',
|
||||
].join('')
|
||||
: [
|
||||
'<div class="relation-avatar-photo relation-avatar-photo--placeholder" aria-label="',
|
||||
escapeHTML(name),
|
||||
'">',
|
||||
escapeHTML(name.charAt(0).toUpperCase() || '?'),
|
||||
'</div>',
|
||||
].join(''),
|
||||
'<img class="relation-avatar-frame" src="',
|
||||
escapeHTML(frameSrc),
|
||||
'" alt="" draggable="false" />',
|
||||
'<figcaption class="relation-name relation-name--',
|
||||
side,
|
||||
'">',
|
||||
escapeHTML(name),
|
||||
'</figcaption>',
|
||||
'</figure>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function cardHTML(item, index) {
|
||||
var parts = partsFor(item);
|
||||
var label = t('cpSpace.levelCard', 'Best friend level {level}').replace(
|
||||
'{level}',
|
||||
String(safeLevel)
|
||||
String(item.level)
|
||||
);
|
||||
var daysText = t('cpSpace.daysValue', '{days} Days').replace(
|
||||
'{days}',
|
||||
String(item.days)
|
||||
);
|
||||
var levelTitle = t('cpSpace.levelValue', 'Lv.{level}').replace(
|
||||
'{level}',
|
||||
String(item.level)
|
||||
);
|
||||
var cardClass =
|
||||
'relation-card relation-card--' + escapeHTML(item.relationType);
|
||||
if (item.relationType !== 'cp') cardClass += ' relation-card--shared';
|
||||
|
||||
return [
|
||||
'<article class="relation-card" data-level="',
|
||||
safeLevel,
|
||||
'<article class="',
|
||||
cardClass,
|
||||
'" data-level="',
|
||||
visualLevel(item),
|
||||
'">',
|
||||
'<img src="',
|
||||
LEVEL_IMAGES[safeLevel],
|
||||
'<img class="relation-card-bg" src="',
|
||||
parts.bg,
|
||||
'" alt="',
|
||||
escapeHTML(label),
|
||||
'" draggable="false" />',
|
||||
'<button class="cancel-hotspot" type="button" data-cancel-index="',
|
||||
index,
|
||||
'" aria-label="',
|
||||
escapeHTML(t('cpSpace.cancel', 'Cancel')),
|
||||
'"></button>',
|
||||
avatarHTML(item.me, 'left', parts.avatar),
|
||||
avatarHTML(item.partner, 'right', parts.avatar),
|
||||
'<img class="relation-heart-art" src="',
|
||||
parts.heart,
|
||||
'" alt="" draggable="false" />',
|
||||
'<div class="relation-days"><strong>',
|
||||
escapeHTML(String(item.days)),
|
||||
'</strong><span>',
|
||||
escapeHTML(t('cpSpace.daysUnit', 'Days')),
|
||||
'</span></div>',
|
||||
'<div class="relation-level relation-level--current"><img src="',
|
||||
parts['level-pill'],
|
||||
'" alt="" draggable="false" /><span>',
|
||||
escapeHTML(levelTitle),
|
||||
'</span></div>',
|
||||
'<div class="relation-level relation-level--next"><img src="',
|
||||
parts['level-pill'],
|
||||
'" alt="" draggable="false" /><span>',
|
||||
escapeHTML(nextLevelLabel(item)),
|
||||
'</span></div>',
|
||||
'<div class="relation-exp">',
|
||||
escapeHTML(expLabel(item)),
|
||||
'</div>',
|
||||
'<div class="relation-progress" aria-label="',
|
||||
escapeHTML(daysText),
|
||||
'"><img class="relation-progress-track" src="',
|
||||
parts['xp-track'],
|
||||
'" alt="" draggable="false" /><span style="width:',
|
||||
item.progressPercent,
|
||||
'%"><img class="relation-progress-fill" src="',
|
||||
parts['xp-fill'],
|
||||
'" alt="" draggable="false" /></span></div>',
|
||||
item.relationType === 'cp'
|
||||
? [
|
||||
'<button class="cancel-button" type="button" data-cancel-index="',
|
||||
index,
|
||||
'"',
|
||||
state.cancelingId === item.relationshipId
|
||||
? ' disabled'
|
||||
: '',
|
||||
'>',
|
||||
'<img class="cancel-button-art" src="',
|
||||
parts.button,
|
||||
'" alt="" draggable="false" />',
|
||||
'<span>',
|
||||
escapeHTML(
|
||||
state.cancelingId === item.relationshipId
|
||||
? t('cpSpace.canceling', 'Canceling...')
|
||||
: t('cpSpace.cancel', 'Cancel')
|
||||
),
|
||||
'</span>',
|
||||
'</button>',
|
||||
].join('')
|
||||
: '',
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function render() {
|
||||
var levels = state.tabs[state.activeTab] || [];
|
||||
if (!levels.length) {
|
||||
var tabState = state.tabs[state.activeTab];
|
||||
if (tabState.loading && !tabState.loaded) {
|
||||
contentPanel.innerHTML =
|
||||
'<div class="empty-state">' +
|
||||
escapeHTML(t('cpSpace.loading', 'Loading...')) +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
if (tabState.error) {
|
||||
contentPanel.innerHTML =
|
||||
'<button class="empty-state empty-state--button" type="button" data-retry-tab="' +
|
||||
escapeHTML(state.activeTab) +
|
||||
'">' +
|
||||
escapeHTML(tabState.error) +
|
||||
'</button>';
|
||||
return;
|
||||
}
|
||||
if (!tabState.relationships.length) {
|
||||
contentPanel.innerHTML =
|
||||
'<div class="empty-state">' +
|
||||
escapeHTML(t('cpSpace.empty', 'No relationship data yet.')) +
|
||||
@ -109,45 +406,124 @@
|
||||
|
||||
contentPanel.innerHTML =
|
||||
'<div class="relation-list">' +
|
||||
levels.map(cardHTML).join('') +
|
||||
tabState.relationships.map(cardHTML).join('') +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function loadTab(tab) {
|
||||
var tabState = state.tabs[tab];
|
||||
var seq = ++state.requestSeq;
|
||||
tabState.loading = true;
|
||||
tabState.error = '';
|
||||
render();
|
||||
|
||||
// 关系事实全部从 gateway profile 接口读取;H5 只负责按 relation_type 分页拉取,不本地推断用户是否有 CP/兄弟/姐妹。
|
||||
return window.HyAppAPI.cpSpace
|
||||
.relationships({
|
||||
relation_type: tab,
|
||||
page: 1,
|
||||
page_size: tab === 'cp' ? 1 : 20,
|
||||
})
|
||||
.then(function (data) {
|
||||
if (seq < state.requestSeq && tab === state.activeTab) return;
|
||||
tabState.relationships = activeRelationships(data, tab);
|
||||
tabState.loaded = true;
|
||||
})
|
||||
.catch(function (error) {
|
||||
tabState.error =
|
||||
(error && error.message) ||
|
||||
t('cpSpace.loadFailed', 'Load failed. Tap to retry.');
|
||||
})
|
||||
.then(function () {
|
||||
tabState.loading = false;
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function buildCommandId(relationshipId) {
|
||||
var random =
|
||||
window.crypto && window.crypto.randomUUID
|
||||
? window.crypto.randomUUID()
|
||||
: String(Date.now()) + '_' + Math.random().toString(16).slice(2);
|
||||
return ['h5_cp_break', relationshipId, random]
|
||||
.join('_')
|
||||
.replace(/[^A-Za-z0-9_-]/g, '_');
|
||||
}
|
||||
|
||||
function confirmBreakup(item) {
|
||||
var message =
|
||||
item.breakupCostCoins > 0
|
||||
? t(
|
||||
'cpSpace.cancelConfirmWithCost',
|
||||
'Canceling this CP will cost {coins} coins. Continue?'
|
||||
).replace('{coins}', formatNumber(item.breakupCostCoins))
|
||||
: t(
|
||||
'cpSpace.cancelConfirmFree',
|
||||
'Cancel this CP relationship?'
|
||||
);
|
||||
return Promise.resolve(window.confirm(message));
|
||||
}
|
||||
|
||||
function handleCancel(index) {
|
||||
var item =
|
||||
state.tabs[state.activeTab].relationships[Number(index)] || null;
|
||||
if (!item || !item.relationshipId || state.cancelingId) return;
|
||||
|
||||
confirmBreakup(item).then(function (confirmed) {
|
||||
if (!confirmed) return;
|
||||
state.cancelingId = item.relationshipId;
|
||||
render();
|
||||
|
||||
// command_id 必须由本次点击生成并随请求提交,服务端用它串联占位、钱包扣费和确认解除,网络重试不会重复扣费。
|
||||
window.HyAppAPI.cpSpace
|
||||
.breakRelationship(item.relationshipId, {
|
||||
command_id: buildCommandId(item.relationshipId),
|
||||
})
|
||||
.then(function (data) {
|
||||
showToast(t('cpSpace.cancelSuccess', 'Canceled.'));
|
||||
if (window.HyAppBridge && window.HyAppBridge.post) {
|
||||
window.HyAppBridge.post('cpSpaceRelationCanceled', {
|
||||
relationship_id: item.relationshipId,
|
||||
relation_type: item.relationType,
|
||||
breakup_cost_coins: readNumber(
|
||||
data && data.breakup_cost_coins
|
||||
),
|
||||
});
|
||||
}
|
||||
state.tabs.cp.loaded = false;
|
||||
return loadTab('cp');
|
||||
})
|
||||
.catch(function (error) {
|
||||
showToast(
|
||||
(error && error.message) ||
|
||||
t('cpSpace.cancelFailed', 'Cancel failed.')
|
||||
);
|
||||
})
|
||||
.then(function () {
|
||||
state.cancelingId = '';
|
||||
render();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function applyExternalData(payload) {
|
||||
var data = payload || {};
|
||||
var nextTabs = {};
|
||||
VALID_TABS.forEach(function (tab) {
|
||||
var raw = data[tab];
|
||||
if (!Array.isArray(raw)) return;
|
||||
var normalized = raw
|
||||
state.tabs[tab].relationships = raw
|
||||
.map(function (item) {
|
||||
return normalizeLevel(
|
||||
typeof item === 'object' ? item.level : item
|
||||
);
|
||||
return normalizeRelationship(item || {}, tab);
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (normalized.length) nextTabs[tab] = normalized;
|
||||
});
|
||||
|
||||
// Flutter 可以在 WebView 注入 window.HyAppCPSpaceData 或派发 hyapp:cp-space-data;
|
||||
// 页面只接受 1-5 的等级编号,避免 H5 自己猜测关系状态或渲染后端未确认的数据。
|
||||
Object.keys(nextTabs).forEach(function (tab) {
|
||||
state.tabs[tab] = nextTabs[tab];
|
||||
.filter(function (item) {
|
||||
return item.relationshipId;
|
||||
});
|
||||
state.tabs[tab].loaded = true;
|
||||
});
|
||||
if (normalizeTab(data.activeTab)) state.activeTab = data.activeTab;
|
||||
setActiveTab(state.activeTab);
|
||||
}
|
||||
|
||||
function handleCancel(index) {
|
||||
var level = (state.tabs[state.activeTab] || [])[Number(index)] || 0;
|
||||
if (window.HyAppBridge && window.HyAppBridge.post) {
|
||||
window.HyAppBridge.post('cpSpaceCancel', {
|
||||
relation_type: state.activeTab,
|
||||
level: level,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
if (backButton) {
|
||||
backButton.addEventListener('click', function () {
|
||||
@ -166,9 +542,13 @@
|
||||
});
|
||||
|
||||
contentPanel.addEventListener('click', function (event) {
|
||||
var target = event.target.closest('[data-cancel-index]');
|
||||
if (!target) return;
|
||||
handleCancel(target.getAttribute('data-cancel-index'));
|
||||
var cancelTarget = event.target.closest('[data-cancel-index]');
|
||||
if (cancelTarget) {
|
||||
handleCancel(cancelTarget.getAttribute('data-cancel-index'));
|
||||
return;
|
||||
}
|
||||
var retryTarget = event.target.closest('[data-retry-tab]');
|
||||
if (retryTarget) loadTab(retryTarget.getAttribute('data-retry-tab'));
|
||||
});
|
||||
|
||||
window.addEventListener('hyapp:cp-space-data', function (event) {
|
||||
|
||||
@ -59,7 +59,7 @@ button {
|
||||
|
||||
.cp-space::before {
|
||||
z-index: 0;
|
||||
background: url('./assets/space-bg.svg') center top / 100% auto repeat-y;
|
||||
background: url('./assets/space-bg@4x.png') center top / 100% auto repeat-y;
|
||||
}
|
||||
|
||||
.cp-space::after {
|
||||
@ -168,26 +168,317 @@ button {
|
||||
.relation-card {
|
||||
position: relative;
|
||||
width: min(355px, calc(100vw - 20px));
|
||||
aspect-ratio: 355 / 203;
|
||||
color: #ff0979;
|
||||
--heart-size: 33.8%;
|
||||
--button-width: 35.8%;
|
||||
--button-height: 14.8%;
|
||||
--accent-color: #ff0979;
|
||||
--accent-shadow: rgba(110, 17, 108, 0.75);
|
||||
--avatar-y: 6.9%;
|
||||
--heart-y: 2.5%;
|
||||
--button-y: 73.9%;
|
||||
border-radius: 10px;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.relation-card img {
|
||||
.relation-card[data-level='2'] {
|
||||
--accent-color: #0967ff;
|
||||
--heart-size: 33.8%;
|
||||
--button-width: 35.8%;
|
||||
--button-height: 18.7%;
|
||||
}
|
||||
|
||||
.relation-card[data-level='3'] {
|
||||
--accent-color: #7c09ff;
|
||||
--heart-size: 40.6%;
|
||||
--button-width: 45.9%;
|
||||
--button-height: 17.2%;
|
||||
--avatar-y: 9.9%;
|
||||
--heart-y: 0;
|
||||
--button-y: 76.4%;
|
||||
}
|
||||
|
||||
.relation-card[data-level='4'] {
|
||||
--accent-color: #ff090d;
|
||||
--heart-size: 37.7%;
|
||||
--button-width: 36.6%;
|
||||
--button-height: 20.7%;
|
||||
}
|
||||
|
||||
.relation-card[data-level='5'] {
|
||||
--accent-color: #ff090d;
|
||||
--heart-size: 42.8%;
|
||||
--button-width: 37.5%;
|
||||
--button-height: 17.2%;
|
||||
--heart-y: 0;
|
||||
--button-y: 76.8%;
|
||||
}
|
||||
|
||||
.relation-card-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
object-fit: fill;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.cancel-hotspot {
|
||||
.relation-avatar {
|
||||
position: absolute;
|
||||
left: 35%;
|
||||
bottom: 10%;
|
||||
width: 30%;
|
||||
height: 18%;
|
||||
z-index: 2;
|
||||
top: var(--avatar-y);
|
||||
width: 28.7%;
|
||||
aspect-ratio: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.relation-avatar--left {
|
||||
left: 5.1%;
|
||||
}
|
||||
|
||||
.relation-avatar--right {
|
||||
right: 5.1%;
|
||||
}
|
||||
|
||||
.relation-avatar-photo,
|
||||
.relation-avatar-frame {
|
||||
position: absolute;
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.relation-avatar-photo {
|
||||
left: 21.6%;
|
||||
top: 17.6%;
|
||||
z-index: 1;
|
||||
width: 56.9%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.relation-avatar-photo--placeholder {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: linear-gradient(135deg, #f65ca8, #7631dc);
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.relation-avatar-frame {
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.relation-name {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 67%;
|
||||
width: 84%;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 1px 4px rgba(93, 23, 88, 0.75);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.relation-name--left {
|
||||
left: 8%;
|
||||
}
|
||||
|
||||
.relation-name--right {
|
||||
right: 8%;
|
||||
}
|
||||
|
||||
.relation-heart-art {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
top: var(--heart-y);
|
||||
display: block;
|
||||
width: var(--heart-size);
|
||||
aspect-ratio: 1;
|
||||
object-fit: contain;
|
||||
transform: translateX(-50%);
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.relation-days {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 50%;
|
||||
top: 23.5%;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
width: 28%;
|
||||
height: 14%;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
0 1px 0 var(--accent-color),
|
||||
0 2px 7px var(--accent-shadow);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.relation-days strong {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.relation-days span {
|
||||
margin-left: 3px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.relation-level {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 55.6%;
|
||||
width: 8.45%;
|
||||
aspect-ratio: 30 / 16;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.relation-level--current {
|
||||
left: 8.45%;
|
||||
}
|
||||
|
||||
.relation-level--next {
|
||||
right: 8.45%;
|
||||
}
|
||||
|
||||
.relation-level img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.relation-level span {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
text-shadow: 0 0 1px #fc8ab1;
|
||||
}
|
||||
|
||||
.relation-progress {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 19.15%;
|
||||
top: 58.1%;
|
||||
width: 62%;
|
||||
height: 3%;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.relation-progress span {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.relation-progress-track,
|
||||
.relation-progress-fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.relation-progress-fill {
|
||||
width: auto;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.relation-exp {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 25%;
|
||||
top: 62.6%;
|
||||
width: 50%;
|
||||
overflow: hidden;
|
||||
color: var(--accent-color);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 0 1px rgba(255, 255, 255, 0.72);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 50%;
|
||||
top: var(--button-y);
|
||||
width: var(--button-width);
|
||||
height: var(--button-height);
|
||||
color: var(--accent-color);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-shadow: 0 0 1px rgba(255, 255, 255, 0.8);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.cancel-button-art {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.cancel-button span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cancel-button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
width: min(355px, calc(100vw - 20px));
|
||||
margin: 0 auto;
|
||||
@ -201,6 +492,11 @@ button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state--button {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[dir='rtl'] .back-button span {
|
||||
transform: rotate(225deg);
|
||||
}
|
||||
|
||||
@ -756,6 +756,26 @@ var default_api = 'https://api.global-interaction.com/';
|
||||
},
|
||||
};
|
||||
|
||||
var cpSpaceAPI = {
|
||||
relationships: function (params) {
|
||||
return request('/api/v1/cp/relationships', {
|
||||
method: 'GET',
|
||||
query: params || {},
|
||||
});
|
||||
},
|
||||
breakRelationship: function (relationshipID, payload) {
|
||||
return request(
|
||||
'/api/v1/cp/relationships/' +
|
||||
encodeURIComponent(relationshipID || '') +
|
||||
'/break',
|
||||
{
|
||||
method: 'POST',
|
||||
body: payload || {},
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// 转盘 H5 只通过 gateway 活动接口读配置、抽奖、查记录和跑马灯提示;页面不再内置 mock 奖品,避免后台资源组未配置时前端仍展示假奖池。
|
||||
// 抽奖 command_id 由页面点击时生成并随请求提交,服务端用它做幂等,网络重试不会重复扣费或重复发奖。
|
||||
var activityWheelAPI = {
|
||||
@ -990,6 +1010,7 @@ var default_api = 'https://api.global-interaction.com/';
|
||||
task: taskAPI,
|
||||
weeklyStar: weeklyStarAPI,
|
||||
cpWeeklyRank: cpWeeklyRankAPI,
|
||||
cpSpace: cpSpaceAPI,
|
||||
inviteActivityReward: inviteActivityRewardAPI,
|
||||
game: gameAPI,
|
||||
};
|
||||
|
||||
@ -607,5 +607,18 @@
|
||||
"cpSpace.tabSister": "Sister",
|
||||
"cpSpace.levelCard": "مستوى أفضل صديق {level}",
|
||||
"cpSpace.cancel": "إلغاء",
|
||||
"cpSpace.empty": "لا توجد بيانات علاقة بعد."
|
||||
"cpSpace.empty": "لا توجد بيانات علاقة بعد.",
|
||||
"cpSpace.loading": "جار التحميل...",
|
||||
"cpSpace.loadFailed": "فشل التحميل. اضغط للمحاولة مرة أخرى.",
|
||||
"cpSpace.unknownUser": "User",
|
||||
"cpSpace.daysUnit": "Days",
|
||||
"cpSpace.daysValue": "{days} Days",
|
||||
"cpSpace.levelValue": "Lv.{level}",
|
||||
"cpSpace.expProgress": "{current}/{next} EXP",
|
||||
"cpSpace.maxLevelExp": "{exp} EXP",
|
||||
"cpSpace.canceling": "جار الإلغاء...",
|
||||
"cpSpace.cancelConfirmWithCost": "إلغاء علاقة CP سيكلف {coins} عملة. هل تريد المتابعة؟",
|
||||
"cpSpace.cancelConfirmFree": "هل تريد إلغاء علاقة CP؟",
|
||||
"cpSpace.cancelSuccess": "تم الإلغاء.",
|
||||
"cpSpace.cancelFailed": "فشل الإلغاء."
|
||||
}
|
||||
|
||||
@ -613,5 +613,18 @@
|
||||
"cpSpace.tabSister": "Sister",
|
||||
"cpSpace.levelCard": "Best friend level {level}",
|
||||
"cpSpace.cancel": "Cancel",
|
||||
"cpSpace.empty": "No relationship data yet."
|
||||
"cpSpace.empty": "No relationship data yet.",
|
||||
"cpSpace.loading": "Loading...",
|
||||
"cpSpace.loadFailed": "Load failed. Tap to retry.",
|
||||
"cpSpace.unknownUser": "User",
|
||||
"cpSpace.daysUnit": "Days",
|
||||
"cpSpace.daysValue": "{days} Days",
|
||||
"cpSpace.levelValue": "Lv.{level}",
|
||||
"cpSpace.expProgress": "{current}/{next} EXP",
|
||||
"cpSpace.maxLevelExp": "{exp} EXP",
|
||||
"cpSpace.canceling": "Canceling...",
|
||||
"cpSpace.cancelConfirmWithCost": "Canceling this CP will cost {coins} coins. Continue?",
|
||||
"cpSpace.cancelConfirmFree": "Cancel this CP relationship?",
|
||||
"cpSpace.cancelSuccess": "Canceled.",
|
||||
"cpSpace.cancelFailed": "Cancel failed."
|
||||
}
|
||||
|
||||
@ -607,5 +607,18 @@
|
||||
"cpSpace.tabSister": "Sister",
|
||||
"cpSpace.levelCard": "Nivel de mejor amigo {level}",
|
||||
"cpSpace.cancel": "Cancelar",
|
||||
"cpSpace.empty": "Aún no hay datos de relación."
|
||||
"cpSpace.empty": "Aún no hay datos de relación.",
|
||||
"cpSpace.loading": "Cargando...",
|
||||
"cpSpace.loadFailed": "Error al cargar. Toca para reintentar.",
|
||||
"cpSpace.unknownUser": "User",
|
||||
"cpSpace.daysUnit": "Days",
|
||||
"cpSpace.daysValue": "{days} Days",
|
||||
"cpSpace.levelValue": "Lv.{level}",
|
||||
"cpSpace.expProgress": "{current}/{next} EXP",
|
||||
"cpSpace.maxLevelExp": "{exp} EXP",
|
||||
"cpSpace.canceling": "Cancelando...",
|
||||
"cpSpace.cancelConfirmWithCost": "Cancelar este CP costará {coins} monedas. ¿Continuar?",
|
||||
"cpSpace.cancelConfirmFree": "¿Cancelar esta relación CP?",
|
||||
"cpSpace.cancelSuccess": "Cancelado.",
|
||||
"cpSpace.cancelFailed": "Error al cancelar."
|
||||
}
|
||||
|
||||
@ -574,5 +574,18 @@
|
||||
"cpSpace.tabSister": "Sister",
|
||||
"cpSpace.levelCard": "Level sahabat terbaik {level}",
|
||||
"cpSpace.cancel": "Batal",
|
||||
"cpSpace.empty": "Belum ada data hubungan."
|
||||
"cpSpace.empty": "Belum ada data hubungan.",
|
||||
"cpSpace.loading": "Memuat...",
|
||||
"cpSpace.loadFailed": "Gagal memuat. Ketuk untuk mencoba lagi.",
|
||||
"cpSpace.unknownUser": "User",
|
||||
"cpSpace.daysUnit": "Days",
|
||||
"cpSpace.daysValue": "{days} Days",
|
||||
"cpSpace.levelValue": "Lv.{level}",
|
||||
"cpSpace.expProgress": "{current}/{next} EXP",
|
||||
"cpSpace.maxLevelExp": "{exp} EXP",
|
||||
"cpSpace.canceling": "Membatalkan...",
|
||||
"cpSpace.cancelConfirmWithCost": "Membatalkan CP ini akan memakai {coins} koin. Lanjutkan?",
|
||||
"cpSpace.cancelConfirmFree": "Batalkan hubungan CP ini?",
|
||||
"cpSpace.cancelSuccess": "Dibatalkan.",
|
||||
"cpSpace.cancelFailed": "Gagal membatalkan."
|
||||
}
|
||||
|
||||
@ -574,5 +574,18 @@
|
||||
"cpSpace.tabSister": "Sister",
|
||||
"cpSpace.levelCard": "En iyi arkadaş seviyesi {level}",
|
||||
"cpSpace.cancel": "İptal",
|
||||
"cpSpace.empty": "Henüz ilişki verisi yok."
|
||||
"cpSpace.empty": "Henüz ilişki verisi yok.",
|
||||
"cpSpace.loading": "Yükleniyor...",
|
||||
"cpSpace.loadFailed": "Yüklenemedi. Tekrar denemek için dokun.",
|
||||
"cpSpace.unknownUser": "User",
|
||||
"cpSpace.daysUnit": "Days",
|
||||
"cpSpace.daysValue": "{days} Days",
|
||||
"cpSpace.levelValue": "Lv.{level}",
|
||||
"cpSpace.expProgress": "{current}/{next} EXP",
|
||||
"cpSpace.maxLevelExp": "{exp} EXP",
|
||||
"cpSpace.canceling": "İptal ediliyor...",
|
||||
"cpSpace.cancelConfirmWithCost": "Bu CP ilişkisini iptal etmek {coins} coin tutar. Devam edilsin mi?",
|
||||
"cpSpace.cancelConfirmFree": "Bu CP ilişkisi iptal edilsin mi?",
|
||||
"cpSpace.cancelSuccess": "İptal edildi.",
|
||||
"cpSpace.cancelFailed": "İptal başarısız."
|
||||
}
|
||||
|
||||
@ -580,5 +580,18 @@
|
||||
"cpSpace.tabSister": "Sister",
|
||||
"cpSpace.levelCard": "挚友等级 {level}",
|
||||
"cpSpace.cancel": "取消",
|
||||
"cpSpace.empty": "暂无关系数据"
|
||||
"cpSpace.empty": "暂无关系数据",
|
||||
"cpSpace.loading": "加载中...",
|
||||
"cpSpace.loadFailed": "加载失败,点击重试",
|
||||
"cpSpace.unknownUser": "用户",
|
||||
"cpSpace.daysUnit": "Days",
|
||||
"cpSpace.daysValue": "{days} Days",
|
||||
"cpSpace.levelValue": "Lv.{level}",
|
||||
"cpSpace.expProgress": "{current}/{next} EXP",
|
||||
"cpSpace.maxLevelExp": "{exp} EXP",
|
||||
"cpSpace.canceling": "取消中...",
|
||||
"cpSpace.cancelConfirmWithCost": "取消 CP 关系需要支付 {coins} 金币,是否继续?",
|
||||
"cpSpace.cancelConfirmFree": "确定取消 CP 关系?",
|
||||
"cpSpace.cancelSuccess": "已取消",
|
||||
"cpSpace.cancelFailed": "取消失败"
|
||||
}
|
||||
|
||||