diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a144fe0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +dist +build +.git +.vscode +*.log diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..23fa8ab --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "always", + "printWidth": 80 +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f2291a5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.tabSize": 4, + "editor.insertSpaces": true +} diff --git a/AGENTS.md b/AGENTS.md index 2f0cdc0..b814f43 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,9 @@ common 里存放环境的js 如果有参数 ?env=test 就走test api 如果没有参数默认是 线上的api -如果env=local 就是本地的api \ No newline at end of file +如果env=local 就是本地的api + +H5 通用主题放在 common/theme.css,新页面默认引用该文件。 +当前通用主题为浅紫色,不使用流光、彩虹渐变或高饱和装饰效果:主色使用 --hy-theme-primary / --hy-theme-primary-strong,按钮使用 --hy-theme-button,页面背景使用 --hy-theme-bg,卡片使用 --hy-theme-surface。 +页面样式不要硬编码主色,优先消费 common/theme.css 里的 CSS 变量;只有 Figma 强制要求 100% 还原时才允许局部覆盖。 + +H5 多语言能力放在 common/i18n.js 和 common/locales/\*.json,当前面向英语 en、阿拉伯语 ar、土耳其语 tr、西班牙语 es。页面文案使用 data-i18n / data-i18n-placeholder / data-i18n-aria 绑定语言包 key;新增页面必须优先复用 common 的语言包机制,不在页面脚本里硬编码多语言字典。 diff --git a/common/api.js b/common/api.js index 91e9d66..def6c2c 100644 --- a/common/api.js +++ b/common/api.js @@ -1,179 +1,251 @@ -var test_api = "https://api-test.global-interaction.com/"; -var local_api = "http://localhost:13000/"; -var default_api = "https://api.global-interaction.com/"; +var test_api = 'https://api-test.global-interaction.com/'; +var local_api = 'http://localhost:13000/'; +var default_api = 'https://api.global-interaction.com/'; (function () { - var API_ENV_KEY = "hyapp_h5_env"; - var ACCESS_TOKEN_KEY = "hyapp_access_token"; - var APP_CODE_KEY = "hyapp_app_code"; - var memoryEnv = ""; - var memoryAccessToken = ""; - var memoryAppCode = ""; + var API_ENV_KEY = 'hyapp_h5_env'; + var ACCESS_TOKEN_KEY = 'hyapp_access_token'; + var APP_CODE_KEY = 'hyapp_app_code'; + var memoryEnv = ''; + var memoryAccessToken = ''; + var memoryAppCode = ''; - function readQuery(name) { - return new URLSearchParams(window.location.search).get(name); - } - - function normalizeBaseURL(value) { - return String(value || "").replace(/\/+$/, "") + "/"; - } - - function isLocalDevOrigin() { - var host = window.location.hostname; - return host === "127.0.0.1" || host === "localhost" || host === "::1"; - } - - function shouldPersist() { - return !isLocalDevOrigin(); - } - - function clearLocalDevCache() { - if (shouldPersist()) return; - window.localStorage.removeItem(API_ENV_KEY); - window.localStorage.removeItem(ACCESS_TOKEN_KEY); - window.localStorage.removeItem(APP_CODE_KEY); - } - - function devProxyBase(path) { - return normalizeBaseURL(window.location.origin + path); - } - - function resolveEnv() { - var env = readQuery("env"); - if (env === "test" || env === "local") { - memoryEnv = env; - if (shouldPersist()) window.localStorage.setItem(API_ENV_KEY, env); - return env; - } - if (memoryEnv) return memoryEnv; - if (!shouldPersist()) return ""; - return window.localStorage.getItem(API_ENV_KEY) || ""; - } - - function resolveBaseURL() { - var env = resolveEnv(); - if (env === "test") return isLocalDevOrigin() ? devProxyBase("/__api_test__/") : normalizeBaseURL(test_api); - if (env === "local") return isLocalDevOrigin() ? devProxyBase("/__api_local__/") : normalizeBaseURL(local_api); - return normalizeBaseURL(default_api); - } - - function buildURL(path, query) { - var url = new URL(path.replace(/^\/+/, ""), resolveBaseURL()); - if (query) { - Object.keys(query).forEach(function (key) { - var value = query[key]; - if (value === undefined || value === null || value === "") return; - url.searchParams.set(key, value); - }); - } - return url.toString(); - } - - function getAccessToken() { - if (memoryAccessToken) return memoryAccessToken; - if (!shouldPersist()) return ""; - return window.localStorage.getItem(ACCESS_TOKEN_KEY) || ""; - } - - function getAppCode() { - var queryAppCode = readQuery("app_code") || readQuery("appCode"); - if (queryAppCode) { - memoryAppCode = queryAppCode; - if (shouldPersist()) window.localStorage.setItem(APP_CODE_KEY, queryAppCode); - return queryAppCode; - } - if (memoryAppCode) return memoryAppCode; - if (!shouldPersist()) return "lalu"; - return window.localStorage.getItem(APP_CODE_KEY) || "lalu"; - } - - function setAccessToken(token) { - memoryAccessToken = token || ""; - if (token) { - if (shouldPersist()) window.localStorage.setItem(ACCESS_TOKEN_KEY, token); - return; - } - if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY); - } - - function setAppCode(appCode) { - memoryAppCode = appCode || ""; - if (appCode) { - if (shouldPersist()) window.localStorage.setItem(APP_CODE_KEY, appCode); - return; - } - if (shouldPersist()) window.localStorage.removeItem(APP_CODE_KEY); - } - - function parseResponse(response) { - return response.text().then(function (text) { - var payload = text ? JSON.parse(text) : {}; - if (!response.ok) { - var httpError = new Error(payload.message || response.statusText || "request_failed"); - httpError.status = response.status; - httpError.payload = payload; - throw httpError; - } - var code = payload && payload.code; - var isErrorCode = (typeof code === "number" && code !== 0) || (typeof code === "string" && code !== "" && code !== "OK"); - if (isErrorCode) { - var apiError = new Error(payload.message || "api_request_failed"); - apiError.code = payload.code; - apiError.request_id = payload.request_id; - apiError.payload = payload; - throw apiError; - } - return payload && Object.prototype.hasOwnProperty.call(payload, "data") ? payload.data : payload; - }); - } - - function request(path, options) { - var opts = options || {}; - var headers = Object.assign({}, opts.headers || {}); - var token = getAccessToken(); - var appCode = getAppCode(); - if (token) headers.Authorization = "Bearer " + token; - if (appCode) headers["X-App-Code"] = appCode; - if (!shouldPersist()) headers["Cache-Control"] = "no-cache"; - - var body = opts.body; - if (body && typeof body === "object" && !(body instanceof FormData)) { - headers["Content-Type"] = headers["Content-Type"] || "application/json"; - body = JSON.stringify(body); + function readQuery(name) { + return new URLSearchParams(window.location.search).get(name); } - return window.fetch(buildURL(path, opts.query), { - method: opts.method || "GET", - headers: headers, - body: body, - cache: shouldPersist() ? "default" : "no-store", - credentials: opts.credentials || "omit" - }).then(parseResponse); - } + function normalizeBaseURL(value) { + return String(value || '').replace(/\/+$/, '') + '/'; + } - window.HyAppAPI = { - baseURL: resolveBaseURL, - buildURL: buildURL, - getAccessToken: getAccessToken, - getAppCode: getAppCode, - setAccessToken: setAccessToken, - setAppCode: setAppCode, - request: request, - get: function (path, query) { - return request(path, { method: "GET", query: query }); + function isLocalDevOrigin() { + var host = window.location.hostname; + return host === '127.0.0.1' || host === 'localhost' || host === '::1'; + } + + function shouldPersist() { + return !isLocalDevOrigin(); + } + + function clearLocalDevCache() { + if (shouldPersist()) return; + window.localStorage.removeItem(API_ENV_KEY); + window.localStorage.removeItem(ACCESS_TOKEN_KEY); + window.localStorage.removeItem(APP_CODE_KEY); + } + + function devProxyBase(path) { + return normalizeBaseURL(window.location.origin + path); + } + + function resolveEnv() { + var env = readQuery('env'); + if (env === 'test' || env === 'local') { + memoryEnv = env; + if (shouldPersist()) window.localStorage.setItem(API_ENV_KEY, env); + return env; + } + if (memoryEnv) return memoryEnv; + if (!shouldPersist()) return ''; + return window.localStorage.getItem(API_ENV_KEY) || ''; + } + + function resolveBaseURL() { + var env = resolveEnv(); + if (env === 'test') + return isLocalDevOrigin() + ? devProxyBase('/__api_test__/') + : normalizeBaseURL(test_api); + if (env === 'local') + return isLocalDevOrigin() + ? devProxyBase('/__api_local__/') + : normalizeBaseURL(local_api); + return normalizeBaseURL(default_api); + } + + function buildURL(path, query) { + var url = new URL(path.replace(/^\/+/, ''), resolveBaseURL()); + if (query) { + Object.keys(query).forEach(function (key) { + var value = query[key]; + if (value === undefined || value === null || value === '') + return; + url.searchParams.set(key, value); + }); + } + return url.toString(); + } + + function getAccessToken() { + if (memoryAccessToken) return memoryAccessToken; + if (!shouldPersist()) return ''; + return window.localStorage.getItem(ACCESS_TOKEN_KEY) || ''; + } + + function getAppCode() { + var queryAppCode = readQuery('app_code') || readQuery('appCode'); + if (queryAppCode) { + memoryAppCode = queryAppCode; + if (shouldPersist()) + window.localStorage.setItem(APP_CODE_KEY, queryAppCode); + return queryAppCode; + } + if (memoryAppCode) return memoryAppCode; + if (!shouldPersist()) return 'lalu'; + return window.localStorage.getItem(APP_CODE_KEY) || 'lalu'; + } + + function setAccessToken(token) { + memoryAccessToken = token || ''; + if (token) { + if (shouldPersist()) + window.localStorage.setItem(ACCESS_TOKEN_KEY, token); + return; + } + if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY); + } + + function setAppCode(appCode) { + memoryAppCode = appCode || ''; + if (appCode) { + if (shouldPersist()) + window.localStorage.setItem(APP_CODE_KEY, appCode); + return; + } + if (shouldPersist()) window.localStorage.removeItem(APP_CODE_KEY); + } + + function parseResponse(response) { + return response.text().then(function (text) { + var payload = text ? JSON.parse(text) : {}; + if (!response.ok) { + var httpError = new Error( + payload.message || response.statusText || 'request_failed' + ); + httpError.status = response.status; + httpError.payload = payload; + throw httpError; + } + var code = payload && payload.code; + var isErrorCode = + (typeof code === 'number' && code !== 0) || + (typeof code === 'string' && code !== '' && code !== 'OK'); + if (isErrorCode) { + var apiError = new Error( + payload.message || 'api_request_failed' + ); + apiError.code = payload.code; + apiError.request_id = payload.request_id; + apiError.payload = payload; + throw apiError; + } + return payload && + Object.prototype.hasOwnProperty.call(payload, 'data') + ? payload.data + : payload; + }); + } + + function request(path, options) { + var opts = options || {}; + var headers = Object.assign({}, opts.headers || {}); + var token = getAccessToken(); + var appCode = getAppCode(); + if (token) headers.Authorization = 'Bearer ' + token; + if (appCode) headers['X-App-Code'] = appCode; + if (!shouldPersist()) headers['Cache-Control'] = 'no-cache'; + + var body = opts.body; + if (body && typeof body === 'object' && !(body instanceof FormData)) { + headers['Content-Type'] = + headers['Content-Type'] || 'application/json'; + body = JSON.stringify(body); + } + + return window + .fetch(buildURL(path, opts.query), { + method: opts.method || 'GET', + headers: headers, + body: body, + cache: shouldPersist() ? 'default' : 'no-store', + credentials: opts.credentials || 'omit', + }) + .then(parseResponse); + } + + var vipAPI = { + me: function () { + return request('/api/v1/vip/me', { method: 'GET' }); + }, + packages: function () { + return request('/api/v1/vip/packages', { method: 'GET' }); + }, + purchase: function (payload) { + return request('/api/v1/vip/purchase', { + method: 'POST', + body: payload || {}, + }); + }, + }; + + var hostAPI = { + searchAgencies: function (shortId, pageSize) { + return request('/api/v1/host/agencies/search', { + method: 'GET', + query: { short_id: shortId, page_size: pageSize || 20 }, + }); + }, + applyToAgency: function (payload) { + return request('/api/v1/host/agency-applications', { + method: 'POST', + body: payload || {}, + }); }, - post: function (path, body, query) { - return request(path, { method: "POST", body: body, query: query }); - }, - put: function (path, body, query) { - return request(path, { method: "PUT", body: body, query: query }); - }, - patch: function (path, body, query) { - return request(path, { method: "PATCH", body: body, query: query }); - }, - delete: function (path, query) { - return request(path, { method: "DELETE", query: query }); - } }; - clearLocalDevCache(); + + var levelAPI = { + overview: function () { + return request("/api/v1/levels/me/overview", { method: "GET" }); + }, + track: function (track) { + return request("/api/v1/levels/tracks/" + encodeURIComponent(track || ""), { + method: "GET", + }); + }, + rewards: function (query) { + return request("/api/v1/levels/rewards", { + method: "GET", + query: query || {}, + }); + }, + }; + + window.HyAppAPI = { + baseURL: resolveBaseURL, + buildURL: buildURL, + getAccessToken: getAccessToken, + getAppCode: getAppCode, + setAccessToken: setAccessToken, + setAppCode: setAppCode, + request: request, + get: function (path, query) { + return request(path, { method: 'GET', query: query }); + }, + post: function (path, body, query) { + return request(path, { method: 'POST', body: body, query: query }); + }, + put: function (path, body, query) { + return request(path, { method: 'PUT', body: body, query: query }); + }, + patch: function (path, body, query) { + return request(path, { method: 'PATCH', body: body, query: query }); + }, + delete: function (path, query) { + return request(path, { method: 'DELETE', query: query }); + }, + vip: vipAPI, + host: hostAPI, + level: levelAPI, + }; + clearLocalDevCache(); })(); diff --git a/common/i18n.js b/common/i18n.js new file mode 100644 index 0000000..1855367 --- /dev/null +++ b/common/i18n.js @@ -0,0 +1,159 @@ +(function () { + var STORAGE_KEY = 'hyapp_h5_lang'; + var DEFAULT_LANG = 'en'; + var SUPPORTED = ['en', 'ar', 'tr', 'es']; + var messages = {}; + var currentLang = ''; + + function queryLang() { + return new URLSearchParams(window.location.search).get('lang') || ''; + } + + function normalizeLang(value) { + var lang = String(value || '') + .trim() + .toLowerCase() + .replace('_', '-'); + if (!lang) return ''; + if (lang.indexOf('ar') === 0) return 'ar'; + if (lang.indexOf('tr') === 0) return 'tr'; + if (lang.indexOf('es') === 0) return 'es'; + return SUPPORTED.indexOf(lang) >= 0 ? lang : ''; + } + + function resolveLang() { + return ( + normalizeLang(queryLang()) || + normalizeLang(window.localStorage.getItem(STORAGE_KEY)) || + normalizeLang(window.navigator.language) || + DEFAULT_LANG + ); + } + + function load(lang) { + var normalized = normalizeLang(lang) || DEFAULT_LANG; + return window + .fetch('../common/locales/' + normalized + '.json', { + cache: 'no-store', + }) + .then(function (response) { + if (!response.ok) throw new Error('locale_not_found'); + return response.json(); + }) + .then(function (json) { + messages = json || {}; + currentLang = normalized; + window.localStorage.setItem(STORAGE_KEY, currentLang); + apply(); + return messages; + }) + .catch(function () { + if (normalized === DEFAULT_LANG) { + messages = {}; + currentLang = DEFAULT_LANG; + apply(); + return messages; + } + return load(DEFAULT_LANG); + }); + } + + function t(key, fallback) { + return Object.prototype.hasOwnProperty.call(messages, key) + ? messages[key] + : fallback || key; + } + + function apply(root) { + var scope = root || document; + document.documentElement.lang = currentLang || DEFAULT_LANG; + document.documentElement.dir = currentLang === 'ar' ? 'rtl' : 'ltr'; + scope.querySelectorAll('[data-i18n]').forEach(function (node) { + node.textContent = t( + node.getAttribute('data-i18n'), + node.textContent + ); + }); + scope + .querySelectorAll('[data-i18n-placeholder]') + .forEach(function (node) { + node.setAttribute( + 'placeholder', + t( + node.getAttribute('data-i18n-placeholder'), + node.getAttribute('placeholder') || '' + ) + ); + }); + scope.querySelectorAll('[data-i18n-aria]').forEach(function (node) { + node.setAttribute( + 'aria-label', + t( + node.getAttribute('data-i18n-aria'), + node.getAttribute('aria-label') || '' + ) + ); + }); + scope.querySelectorAll('[data-lang-option]').forEach(function (node) { + node.classList.toggle( + 'is-active', + node.getAttribute('data-lang-option') === currentLang + ); + }); + scope.querySelectorAll('[data-current-lang]').forEach(function (node) { + node.textContent = (currentLang || DEFAULT_LANG).toUpperCase(); + }); + window.dispatchEvent( + new CustomEvent('hyapp:i18n-ready', { + detail: { lang: currentLang, messages: messages }, + }) + ); + } + + function initLanguageMenu() { + document.addEventListener('click', function (event) { + var toggle = event.target.closest('[data-language-toggle]'); + var menu = document.querySelector('[data-language-menu]'); + if (toggle && menu) { + var expanded = toggle.getAttribute('aria-expanded') === 'true'; + toggle.setAttribute( + 'aria-expanded', + expanded ? 'false' : 'true' + ); + menu.hidden = expanded; + return; + } + var option = event.target.closest('[data-lang-option]'); + if (option) { + if (menu) menu.hidden = true; + var button = document.querySelector('[data-language-toggle]'); + if (button) button.setAttribute('aria-expanded', 'false'); + load(option.getAttribute('data-lang-option')); + return; + } + if (menu && !event.target.closest('.language-switcher')) { + menu.hidden = true; + var languageButton = document.querySelector( + '[data-language-toggle]' + ); + if (languageButton) + languageButton.setAttribute('aria-expanded', 'false'); + } + }); + } + + window.HyAppI18n = { + load: load, + apply: apply, + t: t, + lang: function () { + return currentLang || DEFAULT_LANG; + }, + supported: SUPPORTED.slice(), + }; + + document.addEventListener('DOMContentLoaded', function () { + initLanguageMenu(); + load(resolveLang()); + }); +})(); diff --git a/common/jsbrdge.js b/common/jsbrdge.js deleted file mode 100644 index 3a8cd90..0000000 --- a/common/jsbrdge.js +++ /dev/null @@ -1,8 +0,0 @@ -(function () { - if (!window.HyAppBridge) { - var script = document.createElement("script"); - var current = document.currentScript && document.currentScript.src; - script.src = current ? current.replace(/jsbrdge\.js(?:\?.*)?$/, "jsbridge.js") : "/common/jsbridge.js"; - document.head.appendChild(script); - } -})(); diff --git a/common/jsbridge.js b/common/jsbridge.js index 4c926de..3be34f9 100644 --- a/common/jsbridge.js +++ b/common/jsbridge.js @@ -1,43 +1,69 @@ (function () { - function post(action, payload) { - var message = JSON.stringify({ action: action, payload: payload || {} }); + function post(action, payload) { + var message = JSON.stringify({ + action: action, + payload: payload || {}, + }); - if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) { - return window.flutter_inappwebview.callHandler("HyAppBridge", action, payload || {}); - } - if (window.HyAppBridgeChannel && window.HyAppBridgeChannel.postMessage) { - window.HyAppBridgeChannel.postMessage(message); - return Promise.resolve(); - } - if (window.FlutterBridge && window.FlutterBridge.postMessage) { - window.FlutterBridge.postMessage(message); - return Promise.resolve(); - } - if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.HyAppBridge) { - window.webkit.messageHandlers.HyAppBridge.postMessage({ action: action, payload: payload || {} }); - return Promise.resolve(); + if ( + window.flutter_inappwebview && + window.flutter_inappwebview.callHandler + ) { + return window.flutter_inappwebview.callHandler( + 'HyAppBridge', + action, + payload || {} + ); + } + if ( + window.HyAppBridgeChannel && + window.HyAppBridgeChannel.postMessage + ) { + window.HyAppBridgeChannel.postMessage(message); + return Promise.resolve(); + } + if (window.FlutterBridge && window.FlutterBridge.postMessage) { + window.FlutterBridge.postMessage(message); + return Promise.resolve(); + } + if ( + window.webkit && + window.webkit.messageHandlers && + window.webkit.messageHandlers.HyAppBridge + ) { + window.webkit.messageHandlers.HyAppBridge.postMessage({ + action: action, + payload: payload || {}, + }); + return Promise.resolve(); + } + + window.dispatchEvent( + new CustomEvent('hyapp:bridge-message', { + detail: { action: action, payload: payload || {} }, + }) + ); + return Promise.resolve(); } - window.dispatchEvent(new CustomEvent("hyapp:bridge-message", { detail: { action: action, payload: payload || {} } })); - return Promise.resolve(); - } - - window.HyAppBridge = { - post: post, - back: function () { - return post("back"); - }, - openUser: function (userId) { - return post("openUser", { user_id: String(userId || "") }); - }, - openRoom: function (roomId) { - return post("openRoom", { room_id: String(roomId || "") }); - }, - openRankNote: function (boardType) { - return post("openRankNote", { board_type: String(boardType || "") }); - }, - ready: function (payload) { - return post("ready", payload || {}); - } - }; + window.HyAppBridge = { + post: post, + back: function () { + return post('back'); + }, + openUser: function (userId) { + return post('openUser', { user_id: String(userId || '') }); + }, + openRoom: function (roomId) { + return post('openRoom', { room_id: String(roomId || '') }); + }, + openRankNote: function (boardType) { + return post('openRankNote', { + board_type: String(boardType || ''), + }); + }, + ready: function (payload) { + return post('ready', payload || {}); + }, + }; })(); diff --git a/common/locales/ar.json b/common/locales/ar.json new file mode 100644 index 0000000..f17272e --- /dev/null +++ b/common/locales/ar.json @@ -0,0 +1,22 @@ +{ + "host_center.title": "مركز المضيف", + "host_center.back": "رجوع", + "host_center.change_language": "تغيير اللغة", + "host_center.profile_name": "Namenamename", + "host_center.uid": "UID: 12345678", + "host_center.join_title": "انضم إلى وكالة", + "host_center.join_text": "ابحث عن رقم الوكالة ثم أرسل طلبك.", + "host_center.join_now": "انضم الآن", + "host_center.agency_id": "رقم الوكالة", + "host_center.enter_agency_id": "أدخل رقم الوكالة", + "host_center.search": "بحث", + "host_center.agency": "الوكالة", + "host_center.apply": "تقديم", + "host_center.search_required": "أدخل رقم الوكالة.", + "host_center.searching": "جار البحث...", + "host_center.no_agency": "لم يتم العثور على وكالة في منطقتك.", + "host_center.search_failed": "فشل البحث. حاول لاحقا.", + "host_center.apply_failed": "فشل إرسال الطلب. حاول لاحقا.", + "host_center.apply_success": "تم إرسال الطلب.", + "host_center.applying": "جار الإرسال..." +} diff --git a/common/locales/en.json b/common/locales/en.json new file mode 100644 index 0000000..e2bdf96 --- /dev/null +++ b/common/locales/en.json @@ -0,0 +1,22 @@ +{ + "host_center.title": "Host Center", + "host_center.back": "Back", + "host_center.change_language": "Change language", + "host_center.profile_name": "Namenamename", + "host_center.uid": "UID: 12345678", + "host_center.join_title": "Join Agency", + "host_center.join_text": "Search for an agency ID and submit your application.", + "host_center.join_now": "Join now", + "host_center.agency_id": "Agency ID", + "host_center.enter_agency_id": "Enter agency ID", + "host_center.search": "Search", + "host_center.agency": "Agency", + "host_center.apply": "Apply", + "host_center.search_required": "Enter an agency ID.", + "host_center.searching": "Searching...", + "host_center.no_agency": "No agency found in your region.", + "host_center.search_failed": "Search failed. Try again later.", + "host_center.apply_failed": "Application failed. Try again later.", + "host_center.apply_success": "Application submitted.", + "host_center.applying": "Submitting..." +} diff --git a/common/locales/es.json b/common/locales/es.json new file mode 100644 index 0000000..72f2575 --- /dev/null +++ b/common/locales/es.json @@ -0,0 +1,22 @@ +{ + "host_center.title": "Centro de anfitrión", + "host_center.back": "Volver", + "host_center.change_language": "Cambiar idioma", + "host_center.profile_name": "Namenamename", + "host_center.uid": "UID: 12345678", + "host_center.join_title": "Unirse a una agencia", + "host_center.join_text": "Busca un ID de agencia y envía tu solicitud.", + "host_center.join_now": "Unirse ahora", + "host_center.agency_id": "ID de agencia", + "host_center.enter_agency_id": "Ingresa el ID de agencia", + "host_center.search": "Buscar", + "host_center.agency": "Agencia", + "host_center.apply": "Solicitar", + "host_center.search_required": "Ingresa un ID de agencia.", + "host_center.searching": "Buscando...", + "host_center.no_agency": "No se encontró ninguna agencia en tu región.", + "host_center.search_failed": "La búsqueda falló. Inténtalo más tarde.", + "host_center.apply_failed": "La solicitud falló. Inténtalo más tarde.", + "host_center.apply_success": "Solicitud enviada.", + "host_center.applying": "Enviando..." +} diff --git a/common/locales/id.json b/common/locales/id.json new file mode 100644 index 0000000..d11dfb3 --- /dev/null +++ b/common/locales/id.json @@ -0,0 +1,22 @@ +{ + "host_center.title": "Pusat Host", + "host_center.back": "Kembali", + "host_center.change_language": "Ganti bahasa", + "host_center.profile_name": "Namenamename", + "host_center.uid": "UID: 12345678", + "host_center.join_title": "Gabung Agency", + "host_center.join_text": "Cari ID agency lalu kirim pengajuan.", + "host_center.join_now": "Gabung sekarang", + "host_center.agency_id": "ID Agency", + "host_center.enter_agency_id": "Masukkan ID agency", + "host_center.search": "Cari", + "host_center.agency": "Agency", + "host_center.apply": "Ajukan", + "host_center.search_required": "Masukkan ID agency.", + "host_center.searching": "Mencari...", + "host_center.no_agency": "Agency tidak ditemukan di wilayah Anda.", + "host_center.search_failed": "Pencarian gagal. Coba lagi nanti.", + "host_center.apply_failed": "Pengajuan gagal. Coba lagi nanti.", + "host_center.apply_success": "Pengajuan terkirim.", + "host_center.applying": "Mengirim..." +} diff --git a/common/locales/tr.json b/common/locales/tr.json new file mode 100644 index 0000000..51684d4 --- /dev/null +++ b/common/locales/tr.json @@ -0,0 +1,22 @@ +{ + "host_center.title": "Host Center", + "host_center.back": "Geri", + "host_center.change_language": "Dili değiştir", + "host_center.profile_name": "Namenamename", + "host_center.uid": "UID: 12345678", + "host_center.join_title": "Ajansa Katıl", + "host_center.join_text": "Ajans ID'sini arayın ve başvurunuzu gönderin.", + "host_center.join_now": "Hemen katıl", + "host_center.agency_id": "Ajans ID", + "host_center.enter_agency_id": "Ajans ID girin", + "host_center.search": "Ara", + "host_center.agency": "Ajans", + "host_center.apply": "Başvur", + "host_center.search_required": "Ajans ID girin.", + "host_center.searching": "Aranıyor...", + "host_center.no_agency": "Bölgenizde ajans bulunamadı.", + "host_center.search_failed": "Arama başarısız. Daha sonra deneyin.", + "host_center.apply_failed": "Başvuru başarısız. Daha sonra deneyin.", + "host_center.apply_success": "Başvuru gönderildi.", + "host_center.applying": "Gönderiliyor..." +} diff --git a/common/locales/zh.json b/common/locales/zh.json new file mode 100644 index 0000000..4441b70 --- /dev/null +++ b/common/locales/zh.json @@ -0,0 +1,22 @@ +{ + "host_center.title": "主播中心", + "host_center.back": "返回", + "host_center.change_language": "切换语言", + "host_center.profile_name": "Namenamename", + "host_center.uid": "UID: 12345678", + "host_center.join_title": "加入公会", + "host_center.join_text": "搜索公会短 ID 并提交申请。", + "host_center.join_now": "立即加入", + "host_center.agency_id": "公会 ID", + "host_center.enter_agency_id": "输入公会 ID", + "host_center.search": "搜索", + "host_center.agency": "公会", + "host_center.apply": "申请", + "host_center.search_required": "请输入公会 ID。", + "host_center.searching": "搜索中...", + "host_center.no_agency": "未找到同区域公会。", + "host_center.search_failed": "搜索失败,请稍后重试。", + "host_center.apply_failed": "申请失败,请稍后重试。", + "host_center.apply_success": "申请已提交。", + "host_center.applying": "提交中..." +} diff --git a/common/params.js b/common/params.js index c6e52a3..a154fae 100644 --- a/common/params.js +++ b/common/params.js @@ -1,72 +1,83 @@ (function () { - var TOKEN_KEYS = ["token", "access_token", "accessToken"]; - var APP_CODE_KEYS = ["app_code", "appCode"]; - var USER_READY_EVENT = "hyapp:user-ready"; - var USER_ERROR_EVENT = "hyapp:user-error"; + var TOKEN_KEYS = ['token', 'access_token', 'accessToken']; + var APP_CODE_KEYS = ['app_code', 'appCode']; + var USER_READY_EVENT = 'hyapp:user-ready'; + var USER_ERROR_EVENT = 'hyapp:user-error'; - function query() { - var params = new URLSearchParams(window.location.search); - var hash = window.location.hash || ""; - if (hash.indexOf("?") >= 0) { - new URLSearchParams(hash.split("?").slice(1).join("?")).forEach(function (value, key) { - if (!params.has(key)) params.set(key, value); - }); - } - return params; - } - - function first(params, keys) { - for (var i = 0; i < keys.length; i += 1) { - var value = params.get(keys[i]); - if (value) return value; - } - return ""; - } - - function parse() { - var params = query(); - var token = first(params, TOKEN_KEYS); - var appCode = first(params, APP_CODE_KEYS) || window.localStorage.getItem("hyapp_app_code") || "lalu"; - - if (window.HyAppAPI) { - if (token) window.HyAppAPI.setAccessToken(token); - if (appCode) window.HyAppAPI.setAppCode(appCode); + function query() { + var params = new URLSearchParams(window.location.search); + var hash = window.location.hash || ''; + if (hash.indexOf('?') >= 0) { + new URLSearchParams(hash.split('?').slice(1).join('?')).forEach( + function (value, key) { + if (!params.has(key)) params.set(key, value); + } + ); + } + return params; } - return { - token: token || (window.HyAppAPI && window.HyAppAPI.getAccessToken ? window.HyAppAPI.getAccessToken() : ""), - app_code: appCode, - raw: params + function first(params, keys) { + for (var i = 0; i < keys.length; i += 1) { + var value = params.get(keys[i]); + if (value) return value; + } + return ''; + } + + function parse() { + var params = query(); + var token = first(params, TOKEN_KEYS); + var appCode = + first(params, APP_CODE_KEYS) || + window.localStorage.getItem('hyapp_app_code') || + 'lalu'; + + if (window.HyAppAPI) { + if (token) window.HyAppAPI.setAccessToken(token); + if (appCode) window.HyAppAPI.setAppCode(appCode); + } + + return { + token: + token || + (window.HyAppAPI && window.HyAppAPI.getAccessToken + ? window.HyAppAPI.getAccessToken() + : ''), + app_code: appCode, + raw: params, + }; + } + + function emit(name, detail) { + window.dispatchEvent(new CustomEvent(name, { detail: detail })); + } + + function loadUser() { + var current = parse(); + if (!current.token || !window.HyAppAPI) { + return Promise.resolve(null); + } + return window.HyAppAPI.get('/api/v1/users/me/overview') + .then(function (userInfo) { + current.user = userInfo; + window.HyAppParams.current = current; + emit(USER_READY_EVENT, current); + return current; + }) + .catch(function (error) { + current.error = error; + window.HyAppParams.current = current; + emit(USER_ERROR_EVENT, current); + throw error; + }); + } + + window.HyAppParams = { + parse: parse, + loadUser: loadUser, + current: parse(), + USER_READY_EVENT: USER_READY_EVENT, + USER_ERROR_EVENT: USER_ERROR_EVENT, }; - } - - function emit(name, detail) { - window.dispatchEvent(new CustomEvent(name, { detail: detail })); - } - - function loadUser() { - var current = parse(); - if (!current.token || !window.HyAppAPI) { - return Promise.resolve(null); - } - return window.HyAppAPI.get("/api/v1/users/me/overview").then(function (userInfo) { - current.user = userInfo; - window.HyAppParams.current = current; - emit(USER_READY_EVENT, current); - return current; - }).catch(function (error) { - current.error = error; - window.HyAppParams.current = current; - emit(USER_ERROR_EVENT, current); - throw error; - }); - } - - window.HyAppParams = { - parse: parse, - loadUser: loadUser, - current: parse(), - USER_READY_EVENT: USER_READY_EVENT, - USER_ERROR_EVENT: USER_ERROR_EVENT - }; })(); diff --git a/common/theme.css b/common/theme.css new file mode 100644 index 0000000..7953c5a --- /dev/null +++ b/common/theme.css @@ -0,0 +1,24 @@ +:root { + --hy-theme-bg: #fbf8ff; + --hy-theme-surface: rgba(255, 255, 255, 0.96); + --hy-theme-surface-soft: #fdfaff; + --hy-theme-text: #282333; + --hy-theme-muted: #8d879a; + --hy-theme-line: #f0e8fb; + --hy-theme-primary: #dbc8ff; + --hy-theme-primary-strong: #c5a6f6; + --hy-theme-primary-deep: #7d57c7; + --hy-theme-primary-soft: #f4ebff; + --hy-theme-accent: #cfb8fb; + --hy-theme-success: #31c6a8; + --hy-theme-danger: #d95d72; + --hy-theme-shadow: 0 10px 24px rgba(132, 90, 212, 0.075); + --hy-theme-button: #c5a6f6; + --hy-theme-button-shadow: 0 8px 18px rgba(132, 90, 212, 0.11); + --hy-theme-hero: linear-gradient( + 151deg, + #f0e6ff 0%, + #fffbff 58%, + rgba(251, 248, 255, 0.88) 100% + ); +} diff --git a/docs/Flutter-H5-排行榜对接文档.md b/docs/Flutter-H5-排行榜对接文档.md index cb5e55c..158dc65 100644 --- a/docs/Flutter-H5-排行榜对接文档.md +++ b/docs/Flutter-H5-排行榜对接文档.md @@ -24,22 +24,22 @@ https://{h5-domain}/rank/index.html?token={access_token}&app_code=lalu `env` 规则: -| 参数 | 说明 | -| --- | --- | -| 不传 | H5 请求 `https://api.global-interaction.com/` | -| `env=test` | H5 请求 `https://api-test.global-interaction.com/` | +| 参数 | 说明 | +| ----------- | -------------------------------------------------------- | +| 不传 | H5 请求 `https://api.global-interaction.com/` | +| `env=test` | H5 请求 `https://api-test.global-interaction.com/` | | `env=local` | H5 请求 `http://localhost:13000/`,用于本机 gateway 联调 | ## URL 参数 -| 参数 | 必填 | 说明 | -| --- | --- | --- | -| `token` | 是 | App 登录后拿到的 access token。H5 会作为 `Authorization: Bearer ` 调后端。 | -| `access_token` | 否 | `token` 的兼容别名。 | -| `accessToken` | 否 | `token` 的兼容别名。 | -| `app_code` | 是 | App 编码,当前传 `lalu`。H5 会作为 `X-App-Code` 请求头。 | -| `appCode` | 否 | `app_code` 的兼容别名。 | -| `env` | 否 | API 环境切换,见上表。 | +| 参数 | 必填 | 说明 | +| -------------- | ---- | --------------------------------------------------------------------------------- | +| `token` | 是 | App 登录后拿到的 access token。H5 会作为 `Authorization: Bearer ` 调后端。 | +| `access_token` | 否 | `token` 的兼容别名。 | +| `accessToken` | 否 | `token` 的兼容别名。 | +| `app_code` | 是 | App 编码,当前传 `lalu`。H5 会作为 `X-App-Code` 请求头。 | +| `appCode` | 否 | `app_code` 的兼容别名。 | +| `env` | 否 | API 环境切换,见上表。 | H5 启动后会调用: @@ -61,19 +61,19 @@ X-App-Code: lalu 页面内包含三个榜单,通过 tab 切换: -| H5 Tab | 后端 `board_type` | 说明 | -| --- | --- | --- | -| `Wealth` | `sent` | 财富榜,按用户送礼总金币排行 | -| `Room` | `room` | 房间榜,按房间收礼总金币排行 | -| `Charm` | `received` | 魅力榜,按用户收礼总金币排行 | +| H5 Tab | 后端 `board_type` | 说明 | +| -------- | ----------------- | ---------------------------- | +| `Wealth` | `sent` | 财富榜,按用户送礼总金币排行 | +| `Room` | `room` | 房间榜,按房间收礼总金币排行 | +| `Charm` | `received` | 魅力榜,按用户收礼总金币排行 | 时间维度: -| H5 Tab | 后端 `period` | -| --- | --- | -| `Daily` | `today` | -| `Weekly` | `week` | -| `Monthly` | `month` | +| H5 Tab | 后端 `period` | +| --------- | ------------- | +| `Daily` | `today` | +| `Weekly` | `week` | +| `Monthly` | `month` | 排行榜接口: @@ -102,29 +102,29 @@ Flutter 只需要实现其中一种即可。推荐统一注册 `HyAppBridge`。 ```json { - "action": "openUser", - "payload": { - "user_id": "10001" - } + "action": "openUser", + "payload": { + "user_id": "10001" + } } ``` ### H5 发出的 action -| action | payload | 触发场景 | Flutter 行为 | -| --- | --- | --- | --- | -| `ready` | `{ "page": "rank" }` | H5 初始化完成 | 可关闭加载态 | -| `back` | `{}` | 用户点击左上角返回 | 关闭 WebView 或返回上一页 | -| `openUser` | `{ "user_id": "10001" }` | 点击财富榜/魅力榜用户头像、榜单项 | 打开用户详情页 | -| `openRoom` | `{ "room_id": "room_id" }` | 点击房间榜房间头像、榜单项 | 进入或打开房间详情 | -| `openRankNote` | `{ "board_type": "sent" }` | 点击右上角问号 | 当前 H5 已内置说明页;Flutter 可只打点,不需要跳转 | +| action | payload | 触发场景 | Flutter 行为 | +| -------------- | -------------------------- | --------------------------------- | -------------------------------------------------- | +| `ready` | `{ "page": "rank" }` | H5 初始化完成 | 可关闭加载态 | +| `back` | `{}` | 用户点击左上角返回 | 关闭 WebView 或返回上一页 | +| `openUser` | `{ "user_id": "10001" }` | 点击财富榜/魅力榜用户头像、榜单项 | 打开用户详情页 | +| `openRoom` | `{ "room_id": "room_id" }` | 点击房间榜房间头像、榜单项 | 进入或打开房间详情 | +| `openRankNote` | `{ "board_type": "sent" }` | 点击右上角问号 | 当前 H5 已内置说明页;Flutter 可只打点,不需要跳转 | `board_type` 取值: -| 值 | 说明 | -| --- | --- | -| `sent` | 财富榜 | -| `room` | 房间榜 | +| 值 | 说明 | +| ---------- | ------ | +| `sent` | 财富榜 | +| `room` | 房间榜 | | `received` | 魅力榜 | ## Flutter 示例 @@ -218,19 +218,19 @@ final controller = WebViewController() H5 依赖以下 Gateway HTTP 接口: -| 接口 | 说明 | -| --- | --- | -| `GET /api/v1/users/me/overview` | 解析 token 后获取当前用户信息 | -| `GET /api/v1/activities/user-leaderboards` | 获取排行榜 | +| 接口 | 说明 | +| ------------------------------------------ | ----------------------------- | +| `GET /api/v1/users/me/overview` | 解析 token 后获取当前用户信息 | +| `GET /api/v1/activities/user-leaderboards` | 获取排行榜 | 业务接口返回 envelope: ```json { - "code": "OK", - "message": "ok", - "request_id": "req_xxx", - "data": {} + "code": "OK", + "message": "ok", + "request_id": "req_xxx", + "data": {} } ``` diff --git a/host-center/index.html b/host-center/index.html new file mode 100644 index 0000000..313289f --- /dev/null +++ b/host-center/index.html @@ -0,0 +1,200 @@ + + + + + + Host Center + + + + + +
+ + + + +
+
+
+
+
+ Namenamename +
+
+ UID: 12345678 +
+ +
+
+ +
+
+
+ Join Agency +
+
+ Search for an agency ID and submit your application. +
+
+ +
+ +
+
+

Join Agency

+
+
+
+ +
+ + + +
+
+
+ + +
+ + + + + + + diff --git a/host-center/script.js b/host-center/script.js new file mode 100644 index 0000000..1498acf --- /dev/null +++ b/host-center/script.js @@ -0,0 +1,180 @@ +(function () { + var selectedAgency = null; + + function $(id) { + return document.getElementById(id); + } + + function t(key, fallback) { + return window.HyAppI18n && window.HyAppI18n.t + ? window.HyAppI18n.t(key, fallback) + : fallback || key; + } + + function setStatus(key, kind) { + var node = $('joinAgencyStatus'); + if (!node) return; + if (!key) { + node.hidden = true; + node.textContent = ''; + node.className = 'join-agency-status'; + return; + } + node.hidden = false; + node.className = 'join-agency-status' + (kind ? ' is-' + kind : ''); + node.textContent = t(key); + } + + function setSearching(searching) { + var input = $('agencySearchInput'); + var button = $('agencySearchButton'); + if (input) input.disabled = searching; + if (button) { + button.disabled = searching; + button.textContent = searching + ? t('host_center.searching', 'Searching...') + : t('host_center.search', 'Search'); + } + } + + function setApplying(applying) { + var button = $('joinAgencyApplyButton'); + if (!button) return; + button.disabled = applying || !selectedAgency; + button.textContent = applying + ? t('host_center.applying', 'Submitting...') + : t('host_center.apply', 'Apply'); + } + + function agencyID(agency) { + return String((agency && (agency.agency_id || agency.agencyId)) || ''); + } + + function agencyName(agency) { + return String((agency && agency.name) || ''); + } + + function renderAgency(agency) { + selectedAgency = agency || null; + var result = $('joinAgencyResult'); + if (!result) return; + if (!selectedAgency) { + result.hidden = true; + setApplying(false); + return; + } + var name = + agencyName(selectedAgency) || t('host_center.agency', 'Agency'); + result.hidden = false; + $('agencyName').textContent = name; + $('agencyAccount').textContent = 'ID: ' + agencyID(selectedAgency); + $('agencyInitial').textContent = + name.trim().charAt(0).toUpperCase() || 'A'; + setApplying(false); + } + + function searchAgency(event) { + if (event) event.preventDefault(); + if (!window.HyAppAPI || !window.HyAppAPI.host) return; + var input = $('agencySearchInput'); + var shortID = input ? input.value.trim() : ''; + selectedAgency = null; + renderAgency(null); + if (!shortID) { + setStatus('host_center.search_required', 'error'); + return; + } + setStatus('host_center.searching'); + setSearching(true); + window.HyAppAPI.host + .searchAgencies(shortID, 20) + .then(function (data) { + var items = (data && (data.items || data.agencies)) || []; + if (!items.length) { + setStatus('host_center.no_agency', 'error'); + return; + } + setStatus(''); + renderAgency(items[0]); + }) + .catch(function () { + setStatus('host_center.search_failed', 'error'); + }) + .finally(function () { + setSearching(false); + }); + } + + function applyAgency() { + if (!selectedAgency || !window.HyAppAPI || !window.HyAppAPI.host) + return; + setStatus('host_center.applying'); + setApplying(true); + window.HyAppAPI.host + .applyToAgency({ + agency_id: agencyID(selectedAgency), + command_id: + 'host_apply_' + + Date.now() + + '_' + + Math.random().toString(16).slice(2), + }) + .then(function () { + setStatus('host_center.apply_success', 'success'); + }) + .catch(function () { + setStatus('host_center.apply_failed', 'error'); + }) + .finally(function () { + setApplying(false); + }); + } + + function renderUser(current) { + var user = current && current.user; + var profile = user && (user.profile || user.user || user); + if (!profile) return; + var name = profile.username || profile.name || profile.nickname || ''; + var uid = + profile.display_user_id || + profile.displayUserId || + profile.user_id || + profile.userId || + ''; + if (name && $('profileName')) { + $('profileName').removeAttribute('data-i18n'); + $('profileName').textContent = name; + } + if (uid && $('profileUID')) { + $('profileUID').removeAttribute('data-i18n'); + $('profileUID').textContent = 'UID: ' + uid; + } + } + + function init() { + if (window.HyAppParams) { + window.HyAppParams.parse(); + window.HyAppParams.loadUser() + .then(renderUser) + .catch(function () {}); + } + var form = $('joinAgencyForm'); + if (form) form.addEventListener('submit', searchAgency); + var applyButton = $('joinAgencyApplyButton'); + if (applyButton) applyButton.addEventListener('click', applyAgency); + var backButton = $('backButton'); + if (backButton) { + backButton.addEventListener('click', function () { + if (window.HyAppBridge) window.HyAppBridge.back(); + }); + } + setApplying(false); + } + + window.addEventListener('hyapp:i18n-ready', function () { + setSearching(false); + setApplying(false); + }); + + document.addEventListener('DOMContentLoaded', init); +})(); diff --git a/host-center/style.css b/host-center/style.css new file mode 100644 index 0000000..5b7324d --- /dev/null +++ b/host-center/style.css @@ -0,0 +1,1420 @@ +:root { + --page-bg: var(--hy-theme-bg, #f7f3ff); + --card-bg: var(--hy-theme-surface, rgba(255, 255, 255, 0.96)); + --text: var(--hy-theme-text, #282333); + --muted: var(--hy-theme-muted, #8d879a); + --line: var(--hy-theme-line, #eee7f8); + --mint: var(--hy-theme-primary, #b89af4); + --gold: var(--hy-theme-accent, #f0c774); + --shadow: var(--hy-theme-shadow, 0 10px 26px rgba(109, 69, 189, 0.1)); +} + +* { + box-sizing: border-box; +} + +html, +body { + width: 100%; + min-height: 100%; + margin: 0; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} + +body { + display: flex; + justify-content: center; + background: #111; + color: var(--text); + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Helvetica, + Arial, + sans-serif; +} + +button { + border: 0; + padding: 0; + background: transparent; + color: inherit; + font: inherit; +} + +a { + color: inherit; + text-decoration: none; +} + +[hidden] { + display: none !important; +} + +.host-center { + position: relative; + width: 100%; + max-width: 430px; + min-height: 100vh; + overflow: hidden; + background: + linear-gradient( + 145deg, + rgba(255, 255, 255, 0.82), + rgba(250, 246, 255, 0) 42% + ), + var(--page-bg); +} + +.host-center[data-loading='true'] .title-bar, +.host-center[data-loading='true'] .content, +.host-center[data-loading='true'] .home-indicator { + visibility: hidden; +} + +.loading-mask { + position: absolute; + z-index: 30; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 18px; + min-height: 100vh; + background: radial-gradient( + circle at 50% 35%, + rgba(241, 233, 255, 0.98), + rgba(247, 243, 255, 0.98) 44%, + rgba(247, 243, 255, 0.96) 100% + ); +} + +.loading-mask[hidden] { + display: none; +} + +.runner-stick { + position: relative; + width: 92px; + height: 82px; + color: var(--hy-theme-primary-strong, #8f64df); + filter: drop-shadow(0 8px 14px rgba(143, 100, 223, 0.22)); + animation: runner-bob 0.42s ease-in-out infinite; +} + +.runner-head, +.runner-body, +.runner-arm, +.runner-leg, +.runner-ground { + position: absolute; + display: block; +} + +.runner-head { + top: 5px; + left: 39px; + width: 18px; + height: 18px; + border: 4px solid currentColor; + border-radius: 50%; +} + +.runner-body { + top: 26px; + left: 47px; + width: 5px; + height: 28px; + border-radius: 999px; + background: currentColor; + transform-origin: 50% 3px; + animation: runner-body 0.42s ease-in-out infinite; +} + +.runner-arm, +.runner-leg { + left: 47px; + width: 5px; + border-radius: 999px; + background: currentColor; + transform-origin: 50% 2px; +} + +.runner-arm { + top: 31px; + height: 27px; +} + +.runner-arm-front { + animation: runner-arm-front 0.42s ease-in-out infinite; +} + +.runner-arm-back { + animation: runner-arm-back 0.42s ease-in-out infinite; +} + +.runner-leg { + top: 51px; + height: 33px; +} + +.runner-leg-front { + animation: runner-leg-front 0.42s ease-in-out infinite; +} + +.runner-leg-back { + animation: runner-leg-back 0.42s ease-in-out infinite; +} + +.runner-ground { + right: 6px; + bottom: 2px; + width: 70px; + height: 4px; + overflow: hidden; + border-radius: 999px; + background: rgba(143, 100, 223, 0.18); +} + +.runner-ground::before, +.runner-ground::after { + content: ''; + position: absolute; + top: 0; + width: 24px; + height: 4px; + border-radius: 999px; + background: currentColor; + animation: runner-ground 0.6s linear infinite; +} + +.runner-ground::after { + animation-delay: 0.3s; +} + +.loading-copy { + color: var(--hy-theme-primary-deep, #6d45bd); + font-size: 15px; + font-weight: 900; + letter-spacing: 0; +} + +.hero-bg { + position: absolute; + inset: 0 0 auto; + height: 215px; + background: var( + --hy-theme-hero, + linear-gradient( + 151deg, + #f0e6ff 0%, + #fffbff 58%, + rgba(250, 246, 255, 0.82) 100% + ) + ); + clip-path: polygon(0 0, 100% 0, 100% 76%, 70% 47%, 40% 58%, 0 78%); +} + +.title-bar, +.content, +.home-indicator { + position: relative; + z-index: 1; +} +.title-bar { + display: grid; + grid-template-columns: 52px 1fr 52px; + align-items: center; + z-index: 3; + height: 60px; + padding: 0 14px; + direction: ltr; +} + +.title-bar h1 { + margin: 0; + color: var(--hy-theme-text, #282333); + font-size: 22px; + font-weight: 900; + line-height: 1; + text-align: center; +} + +.back-button { + display: flex; + align-items: center; + justify-content: flex-start; + width: 44px; + height: 44px; +} + +.back-button svg { + width: 28px; + height: 28px; +} + +.back-button path { + fill: none; + stroke: var(--hy-theme-text, #282333); + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 3; +} + +.language-switcher { + position: relative; + justify-self: end; +} + +.language-button { + justify-self: end; + display: flex; + align-items: center; + justify-content: center; + width: 42px; + height: 30px; + border: 1px solid rgba(35, 40, 46, 0.12); + border-radius: 15px; + background: rgba(255, 255, 255, 0.68); + color: var(--hy-theme-text, #282333); + font-size: 13px; + font-weight: 900; + box-shadow: 0 4px 12px rgba(43, 118, 107, 0.08); +} + +.language-menu { + position: absolute; + z-index: 4; + top: calc(100% + 7px); + right: 0; + display: grid; + gap: 2px; + min-width: 58px; + padding: 5px; + border: 1px solid rgba(35, 40, 46, 0.1); + border-radius: 8px; + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 10px 26px rgba(36, 46, 60, 0.14); +} + +.language-menu button { + min-height: 28px; + border-radius: 6px; + color: var(--hy-theme-text, #282333); + font-size: 12px; + font-weight: 900; +} + +.language-menu button.is-active { + background: var(--hy-theme-primary-soft, #f4ebff); + color: var(--hy-theme-primary-deep, #7d57c7); +} + +.language-menu[hidden] { + display: none; +} + +.content { + display: flex; + flex-direction: column; + gap: 15px; + padding: 8px 14px 90px; +} + +.policy-content { + padding-top: 18px; +} + +.card { + width: 100%; + border-radius: 8px; + background: var(--card-bg); + box-shadow: var(--shadow); +} + +.profile-card { + display: flex; + align-items: center; + min-height: 103px; + padding: 16px 24px; +} + +.avatar-shell { + position: relative; + width: 64px; + height: 64px; + flex: 0 0 64px; +} + +.avatar-image, +.avatar-fallback { + display: block; + width: 64px; + height: 64px; + border-radius: 50%; +} + +.avatar-image { + object-fit: cover; +} + +.avatar-image[hidden], +.avatar-fallback[hidden] { + display: none; +} + +.profile-copy { + min-width: 0; + margin-left: 20px; +} + +.name { + color: var(--hy-theme-text, #282333); + font-size: 20px; + font-weight: 900; + line-height: 1.1; +} + +.meta, +.agency { + color: var(--muted); + font-size: 13px; + font-weight: 750; + line-height: 1.2; +} + +.meta { + margin-top: 6px; +} + +.agency { + display: flex; + align-items: center; + max-width: 100%; + margin-top: 6px; +} + +.agency span:first-child { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.join-agency-card { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + min-height: 86px; + padding: 17px 18px; +} + +.join-agency-copy { + min-width: 0; +} + +.join-agency-title { + color: var(--hy-theme-text, #282333); + font-size: 17px; + font-weight: 900; + line-height: 1.2; +} + +.join-agency-text { + margin-top: 5px; + color: var(--muted); + font-size: 13px; + font-weight: 750; + line-height: 1.35; +} + +.join-agency-action { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 38px; + flex: 0 0 auto; + padding: 0 15px; + border-radius: 8px; + background: var(--hy-theme-button, #c5a6f6); + color: #ffffff; + font-size: 13px; + font-weight: 900; + box-shadow: var( + --hy-theme-button-shadow, + 0 8px 18px rgba(132, 90, 212, 0.11) + ); + white-space: nowrap; +} + +.chevron { + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 8px; + color: #8b8e94; + font-size: 25px; + font-weight: 500; + line-height: 0.8; +} + +.metrics-card { + display: grid; + grid-template-columns: 1fr 1px 1fr; + min-height: 111px; + padding: 23px 24px 20px; +} + +.metric { + min-width: 0; +} + +.metric:first-child { + padding-right: 20px; +} + +.gift-metric { + padding-left: 25px; +} + +.vertical-line { + width: 1px; + background: var(--line); +} + +.label { + min-height: 36px; + color: var(--hy-theme-muted, #8d879a); + font-size: 16px; + font-weight: 750; + line-height: 1.12; +} + +.value { + margin-top: 4px; + color: #292d33; + font-size: 25px; + font-weight: 900; + line-height: 1; +} + +.gift-value, +.income-value { + display: flex; + align-items: center; +} + +.gift-value { + margin-top: 7px; + color: #252a30; + font-size: 24px; + font-weight: 900; + line-height: 1; +} + +.gift-value .amount-text { + min-width: 0; + overflow-wrap: anywhere; +} + +.dollar-symbol { + display: inline-flex; + align-items: center; + justify-content: center; + width: 25px; + height: 25px; + flex: 0 0 25px; + margin-right: 8px; + border-radius: 50%; + background: linear-gradient(145deg, #ffe179 0%, #ffb62d 52%, #f49b20 100%); + color: #fff; + font-size: 18px; + font-weight: 950; + line-height: 1; + box-shadow: + inset 0 1px 2px rgba(255, 255, 255, 0.7), + 0 1px 2px rgba(195, 117, 12, 0.24); +} + +.income-card { + display: flex; + flex-direction: column; + justify-content: center; + min-height: 143px; + padding: 18px 24px; +} + +.income-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.income-title { + display: flex; + align-items: center; + min-width: 0; + color: var(--hy-theme-text, #282333); + font-size: 17px; + font-weight: 900; +} + +.income-title svg { + width: 17px; + height: 17px; + margin-left: 7px; +} + +.income-title circle, +.income-title path { + fill: none; + stroke: #272b31; + stroke-linecap: round; + stroke-width: 2; +} + +.cash-link { + display: flex; + align-items: center; + flex: 0 0 auto; + color: var(--mint); + font-size: 13px; + font-weight: 900; +} + +.cash-link .chevron { + margin-left: 5px; + color: var(--mint); + font-size: 20px; +} + +.income-value { + margin-top: 14px; + max-width: 100%; + color: #23272d; + font-size: 34px; + font-weight: 950; + line-height: 1.05; +} + +.income-currency { + flex: 0 0 auto; + margin-right: 6px; + color: var(--gold); + font-size: 28px; + font-weight: 950; + line-height: 1; +} + +.income-value .amount-text { + min-width: 0; + overflow-wrap: anywhere; +} + +.income-value .chevron { + margin-left: 9px; + color: #b4b6ba; + font-size: 24px; +} + +.policy-entry-card { + min-height: 64px; + padding: 0 16px; +} + +.policy-entry-link { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 64px; +} + +.policy-entry-left { + display: flex; + align-items: center; + min-width: 0; +} + +.policy-entry-left svg { + width: 24px; + height: 24px; + flex: 0 0 24px; + margin-right: 18px; +} + +.policy-entry-left svg path { + fill: none; + stroke: #55585f; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.2; +} + +.policy-entry-left span { + min-width: 0; + overflow: hidden; + color: #464951; + font-size: 16px; + font-weight: 850; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.policy-entry-link > .chevron { + flex: 0 0 auto; + margin-left: 12px; + color: var(--hy-theme-muted, #8d879a); + font-size: 24px; +} + +@keyframes runner-bob { + 0%, + 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-5px); + } +} + +@keyframes runner-body { + 0%, + 100% { + transform: rotate(-7deg); + } + 50% { + transform: rotate(7deg); + } +} + +@keyframes runner-arm-front { + 0%, + 100% { + transform: rotate(62deg); + } + 50% { + transform: rotate(-58deg); + } +} + +@keyframes runner-arm-back { + 0%, + 100% { + transform: rotate(-58deg); + } + 50% { + transform: rotate(62deg); + } +} + +@keyframes runner-leg-front { + 0%, + 100% { + transform: rotate(-58deg); + } + 50% { + transform: rotate(54deg); + } +} + +@keyframes runner-leg-back { + 0%, + 100% { + transform: rotate(54deg); + } + 50% { + transform: rotate(-58deg); + } +} + +@keyframes runner-ground { + 0% { + transform: translateX(78px); + } + 100% { + transform: translateX(-30px); + } +} + +.policy-card { + min-height: 184px; + padding: 18px 16px 16px; +} + +.policy-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 13px; +} + +.policy-head h2 { + min-width: 0; + margin: 0; + overflow: hidden; + color: var(--hy-theme-text, #282333); + font-size: 17px; + font-weight: 900; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.policy-region { + flex: 0 1 auto; + overflow: hidden; + color: var(--hy-theme-muted, #8d879a); + font-size: 12px; + font-weight: 800; + line-height: 1.2; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + +.policy-status { + display: flex; + align-items: center; + justify-content: center; + min-height: 112px; + border: 1px dashed #e5e8ea; + border-radius: 8px; + color: var(--hy-theme-muted, #8d879a); + font-size: 14px; + font-weight: 800; + text-align: center; +} + +.policy-list { + display: grid; + gap: 10px; +} + +.policy-status[hidden], +.policy-list[hidden] { + display: none; +} + +.policy-row { + display: grid; + gap: 10px; + padding: 12px; + border: 1px solid var(--hy-theme-line, #eee7f8); + border-radius: 8px; + background: #fbfcfc; +} + +.policy-row-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.policy-level { + color: var(--hy-theme-text, #282333); + font-size: 16px; + font-weight: 950; +} + +.policy-effective-day { + color: var(--hy-theme-muted, #8d879a); + font-size: 12px; + font-weight: 850; +} + +.policy-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; +} + +.policy-cell { + min-width: 0; +} + +.policy-cell-label { + overflow: hidden; + color: #96989d; + font-size: 11px; + font-weight: 800; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.policy-cell-value { + margin-top: 4px; + overflow-wrap: anywhere; + color: #292d33; + font-size: 14px; + font-weight: 900; + line-height: 1.2; +} + +.daily-modal[hidden] { + display: none; +} + +.daily-modal { + position: fixed; + z-index: 20; + inset: 0; + display: flex; + align-items: flex-end; + justify-content: center; +} + +.daily-backdrop { + position: absolute; + inset: 0; + background: rgba(15, 21, 28, 0.38); +} + +.daily-sheet { + position: relative; + display: flex; + flex-direction: column; + width: min(430px, 100%); + max-height: 80vh; + max-height: 80dvh; + overflow: hidden; + border-radius: 16px 16px 0 0; + background: #fff; + box-shadow: 0 -14px 34px rgba(23, 31, 42, 0.16); +} + +.daily-sheet-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 58px; + padding: 16px 18px 12px; + border-bottom: 1px solid var(--hy-theme-line, #eee7f8); +} + +.daily-sheet-head h2 { + margin: 0; + color: var(--hy-theme-text, #282333); + font-size: 18px; + font-weight: 950; + line-height: 1.2; +} + +.daily-close { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 50%; + background: var(--hy-theme-primary-soft, #efe6ff); + color: var(--hy-theme-primary-deep, #6d45bd); + font-size: 25px; + font-weight: 500; + line-height: 1; +} + +.daily-summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 13px 18px; + border-bottom: 1px solid #f0f2f3; + color: var(--hy-theme-muted, #8d879a); + font-size: 14px; + font-weight: 850; +} + +.daily-summary strong { + color: var(--hy-theme-text, #282333); + font-size: 20px; + font-weight: 950; + overflow-wrap: anywhere; +} + +.daily-list { + display: grid; + flex: 1 1 auto; + gap: 10px; + min-height: 0; + overflow-y: auto; + padding: 14px 18px max(18px, env(safe-area-inset-bottom)); + -webkit-overflow-scrolling: touch; +} + +.daily-row { + display: grid; + gap: 10px; + padding: 12px; + border: 1px solid var(--hy-theme-line, #eee7f8); + border-radius: 8px; + background: #fbfcfc; +} + +.daily-date { + color: var(--hy-theme-text, #282333); + font-size: 15px; + font-weight: 950; + line-height: 1.2; +} + +.daily-values { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.daily-cell { + min-width: 0; +} + +.daily-cell span { + display: block; + overflow: hidden; + color: #96989d; + font-size: 12px; + font-weight: 800; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.daily-cell strong { + display: block; + margin-top: 4px; + overflow-wrap: anywhere; + color: #292d33; + font-size: 16px; + font-weight: 950; + line-height: 1.2; +} + +.daily-empty { + display: flex; + align-items: center; + justify-content: center; + min-height: 92px; + border: 1px dashed #e5e8ea; + border-radius: 8px; + color: var(--hy-theme-muted, #8d879a); + font-size: 14px; + font-weight: 850; + text-align: center; +} + +.join-agency-modal[hidden] { + display: none; +} + +.join-agency-modal { + position: fixed; + z-index: 24; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 18px; +} + +.join-agency-backdrop { + position: absolute; + inset: 0; + background: rgba(15, 21, 28, 0.38); +} + +.join-agency-dialog { + position: relative; + display: flex; + flex-direction: column; + width: min(394px, 100%); + max-height: 78vh; + max-height: 78dvh; + overflow: hidden; + border-radius: 14px; + background: #fff; + box-shadow: 0 18px 44px rgba(23, 31, 42, 0.2); +} + +.join-agency-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 58px; + padding: 16px 18px 12px; + border-bottom: 1px solid var(--hy-theme-line, #eee7f8); +} + +.join-agency-head h2 { + margin: 0; + color: var(--hy-theme-text, #282333); + font-size: 18px; + font-weight: 950; + line-height: 1.2; +} + +.join-agency-close { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 50%; + background: var(--hy-theme-primary-soft, #efe6ff); + color: var(--hy-theme-primary-deep, #6d45bd); + font-size: 25px; + font-weight: 500; + line-height: 1; +} + +.join-agency-modal-body { + display: grid; + flex: 1 1 auto; + gap: 12px; + min-height: 0; + overflow-y: auto; + padding: 16px 18px max(18px, env(safe-area-inset-bottom)); + -webkit-overflow-scrolling: touch; +} + +.join-agency-field { + display: grid; + gap: 10px; +} + +.join-agency-field > span, +.join-agency-section-title { + color: var(--hy-theme-text, #282333); + font-size: 16px; + font-weight: 900; + line-height: 1.2; +} + +.join-agency-search-control { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; +} + +.join-agency-search-control input { + width: 100%; + height: 44px; + min-width: 0; + border: 1px solid var(--hy-theme-line, #eee7f8); + border-radius: 8px; + outline: none; + background: var(--hy-theme-surface-soft, #fbf8ff); + color: var(--hy-theme-text, #282333); + font: inherit; + font-size: 15px; + font-weight: 750; + padding: 0 12px; +} + +.join-agency-search-control input::placeholder { + color: #b3b5ba; +} + +.join-agency-search-button, +.join-agency-apply-button, +.join-agency-cancel-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + border-radius: 8px; + font-size: 15px; + font-weight: 900; +} + +.join-agency-search-button { + min-width: 86px; + padding: 0 16px; + background: var(--hy-theme-button, #c5a6f6); + color: #fff; + box-shadow: var( + --hy-theme-button-shadow, + 0 8px 18px rgba(132, 90, 212, 0.11) + ); +} + +.join-agency-search-button:disabled, +.join-agency-apply-button:disabled, +.join-agency-cancel-button:disabled { + opacity: 0.58; +} + +.join-agency-status { + min-height: 38px; + border-radius: 8px; + padding: 10px 12px; + background: var(--hy-theme-surface-soft, #fbf8ff); + color: var(--hy-theme-muted, #8d879a); + font-size: 13px; + font-weight: 800; + line-height: 1.3; +} + +.join-agency-status.is-error { + color: var(--hy-theme-danger, #d95d72); +} + +.join-agency-status.is-success { + color: var(--hy-theme-success, #31c6a8); +} + +.join-agency-modal-card { + display: grid; + gap: 14px; + padding: 14px; + border: 1px solid var(--hy-theme-line, #eee7f8); + border-radius: 8px; + background: #fbfcfc; +} + +.join-agency-profile-row { + display: flex; + align-items: center; + gap: 14px; + min-width: 0; +} + +.join-agency-avatar { + display: flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + flex: 0 0 56px; + overflow: hidden; + border-radius: 50%; + background: var(--hy-theme-primary, #dbc8ff); + color: var(--hy-theme-primary-deep, #6d45bd); + font-size: 21px; + font-weight: 950; +} + +.join-agency-avatar img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +.join-agency-profile-copy { + min-width: 0; + flex: 1; +} + +.join-agency-profile-name { + overflow: hidden; + color: var(--hy-theme-text, #282333); + font-size: 18px; + font-weight: 900; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.join-agency-profile-account { + margin-top: 6px; + overflow: hidden; + color: var(--hy-theme-muted, #8d879a); + font-size: 13px; + font-weight: 800; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.join-agency-apply-button { + width: 100%; + background: var(--hy-theme-button, #c5a6f6); + color: #fff; + box-shadow: var( + --hy-theme-button-shadow, + 0 8px 18px rgba(132, 90, 212, 0.11) + ); +} + +.join-agency-cancel-button { + width: 100%; + border: 1px solid #e9edf0; + background: #fff; + color: #4b4e55; +} + +.join-agency-pending-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.join-agency-pending-head span { + flex: 0 0 auto; + color: #f2a329; + font-size: 13px; + font-weight: 900; +} + +.join-agency-pending-note { + margin: 0; + color: var(--hy-theme-muted, #8d879a); + font-size: 13px; + font-weight: 800; + line-height: 1.35; +} + +.daily-modal-open { + overflow: hidden; +} + +.home-indicator { + position: absolute; + left: 50%; + bottom: 8px; + width: 154px; + height: 5px; + border-radius: 999px; + background: #050506; + transform: translateX(-50%); +} + +[dir='rtl'] .content { + direction: rtl; +} + +[dir='rtl'] .profile-copy { + margin-right: 20px; + margin-left: 0; + text-align: right; +} + +[dir='rtl'] .income-title svg { + margin-right: 7px; + margin-left: 0; +} + +[dir='rtl'] .dollar-symbol, +[dir='rtl'] .income-currency { + margin-right: 0; + margin-left: 8px; +} + +[dir='rtl'] .chevron { + margin-right: 8px; + margin-left: 0; + transform: rotate(180deg); +} + +[dir='rtl'] .cash-link .chevron { + margin-right: 5px; + margin-left: 0; +} + +[dir='rtl'] .policy-entry-left svg { + margin-right: 0; + margin-left: 18px; +} + +[dir='rtl'] .policy-entry-link > .chevron { + margin-right: 12px; + margin-left: 0; +} + +[dir='rtl'] .policy-region { + text-align: left; +} + +[dir='rtl'] .daily-sheet { + direction: rtl; +} + +[dir='rtl'] .join-agency-profile-copy, +[dir='rtl'] .join-agency-field { + text-align: right; +} + +@media (max-width: 360px) { + .join-agency-search-control { + grid-template-columns: 1fr; + } + + .join-agency-search-button { + width: 100%; + } +} + +.avatar-shell::before { + content: 'N'; + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + border-radius: 50%; + background: var(--hy-theme-primary, #dbc8ff); + color: var(--hy-theme-primary-deep, #6d45bd); + font-size: 24px; + font-weight: 950; +} + +.join-agency-dialog-static { + display: flex; + flex-direction: column; + width: 100%; + overflow: hidden; + border-radius: 14px; + background: #fff; + box-shadow: 0 18px 44px rgba(23, 31, 42, 0.12); +} + +@media (max-width: 360px) { + .content { + padding-right: 12px; + padding-left: 12px; + } + + .profile-card, + .metrics-card, + .income-card { + padding-right: 18px; + padding-left: 18px; + } + + .profile-copy { + margin-left: 14px; + } + + .gift-metric { + padding-left: 18px; + } + + .income-value { + font-size: 29px; + } + + .policy-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + [dir='rtl'] .profile-copy { + margin-right: 14px; + margin-left: 0; + } +} diff --git a/level/assets/component/avatar-charm.png b/level/assets/component/avatar-charm.png new file mode 100644 index 0000000..cb8336a Binary files /dev/null and b/level/assets/component/avatar-charm.png differ diff --git a/level/assets/component/avatar-frame-charm.png b/level/assets/component/avatar-frame-charm.png new file mode 100644 index 0000000..6b27902 Binary files /dev/null and b/level/assets/component/avatar-frame-charm.png differ diff --git a/level/assets/component/avatar-frame-game.png b/level/assets/component/avatar-frame-game.png new file mode 100644 index 0000000..6b27902 Binary files /dev/null and b/level/assets/component/avatar-frame-game.png differ diff --git a/level/assets/component/avatar-frame-wealth.png b/level/assets/component/avatar-frame-wealth.png new file mode 100644 index 0000000..6b27902 Binary files /dev/null and b/level/assets/component/avatar-frame-wealth.png differ diff --git a/level/assets/component/avatar-game.png b/level/assets/component/avatar-game.png new file mode 100644 index 0000000..cb8336a Binary files /dev/null and b/level/assets/component/avatar-game.png differ diff --git a/level/assets/component/avatar-wealth.png b/level/assets/component/avatar-wealth.png new file mode 100644 index 0000000..cb8336a Binary files /dev/null and b/level/assets/component/avatar-wealth.png differ diff --git a/level/assets/component/bg-charm.png b/level/assets/component/bg-charm.png new file mode 100644 index 0000000..96c9acb Binary files /dev/null and b/level/assets/component/bg-charm.png differ diff --git a/level/assets/component/bg-game.png b/level/assets/component/bg-game.png new file mode 100644 index 0000000..96c9acb Binary files /dev/null and b/level/assets/component/bg-game.png differ diff --git a/level/assets/component/bg-wealth.png b/level/assets/component/bg-wealth.png new file mode 100644 index 0000000..96c9acb Binary files /dev/null and b/level/assets/component/bg-wealth.png differ diff --git a/level/assets/component/button-gold.png b/level/assets/component/button-gold.png new file mode 100644 index 0000000..4439c48 Binary files /dev/null and b/level/assets/component/button-gold.png differ diff --git a/level/assets/component/button-purple.png b/level/assets/component/button-purple.png new file mode 100644 index 0000000..4591774 Binary files /dev/null and b/level/assets/component/button-purple.png differ diff --git a/level/assets/component/card-charm.png b/level/assets/component/card-charm.png new file mode 100644 index 0000000..0f8eca0 Binary files /dev/null and b/level/assets/component/card-charm.png differ diff --git a/level/assets/component/card-game.png b/level/assets/component/card-game.png new file mode 100644 index 0000000..30c1cf6 Binary files /dev/null and b/level/assets/component/card-game.png differ diff --git a/level/assets/component/card-wealth.png b/level/assets/component/card-wealth.png new file mode 100644 index 0000000..92857cb Binary files /dev/null and b/level/assets/component/card-wealth.png differ diff --git a/level/assets/component/charm-frame-1.png b/level/assets/component/charm-frame-1.png new file mode 100644 index 0000000..817a531 Binary files /dev/null and b/level/assets/component/charm-frame-1.png differ diff --git a/level/assets/component/charm-frame-2.png b/level/assets/component/charm-frame-2.png new file mode 100644 index 0000000..1a08e7b Binary files /dev/null and b/level/assets/component/charm-frame-2.png differ diff --git a/level/assets/component/charm-frame-3.png b/level/assets/component/charm-frame-3.png new file mode 100644 index 0000000..a1030a5 Binary files /dev/null and b/level/assets/component/charm-frame-3.png differ diff --git a/level/assets/component/charm-frame-4.png b/level/assets/component/charm-frame-4.png new file mode 100644 index 0000000..718fa53 Binary files /dev/null and b/level/assets/component/charm-frame-4.png differ diff --git a/level/assets/component/charm-frame-5.png b/level/assets/component/charm-frame-5.png new file mode 100644 index 0000000..35066be Binary files /dev/null and b/level/assets/component/charm-frame-5.png differ diff --git a/level/assets/component/charm-icon-1.png b/level/assets/component/charm-icon-1.png new file mode 100644 index 0000000..444c95d Binary files /dev/null and b/level/assets/component/charm-icon-1.png differ diff --git a/level/assets/component/charm-icon-2.png b/level/assets/component/charm-icon-2.png new file mode 100644 index 0000000..444c95d Binary files /dev/null and b/level/assets/component/charm-icon-2.png differ diff --git a/level/assets/component/charm-icon-3.png b/level/assets/component/charm-icon-3.png new file mode 100644 index 0000000..515452f Binary files /dev/null and b/level/assets/component/charm-icon-3.png differ diff --git a/level/assets/component/charm-icon-4.png b/level/assets/component/charm-icon-4.png new file mode 100644 index 0000000..22a47c3 Binary files /dev/null and b/level/assets/component/charm-icon-4.png differ diff --git a/level/assets/component/charm-icon-5.png b/level/assets/component/charm-icon-5.png new file mode 100644 index 0000000..6ff4fc2 Binary files /dev/null and b/level/assets/component/charm-icon-5.png differ diff --git a/level/assets/component/corner-gold.png b/level/assets/component/corner-gold.png new file mode 100644 index 0000000..37b4d4a Binary files /dev/null and b/level/assets/component/corner-gold.png differ diff --git a/level/assets/component/corner-purple.png b/level/assets/component/corner-purple.png new file mode 100644 index 0000000..37b4d4a Binary files /dev/null and b/level/assets/component/corner-purple.png differ diff --git a/level/assets/component/decor-charm.png b/level/assets/component/decor-charm.png new file mode 100644 index 0000000..479243c Binary files /dev/null and b/level/assets/component/decor-charm.png differ diff --git a/level/assets/component/decor-game.png b/level/assets/component/decor-game.png new file mode 100644 index 0000000..479243c Binary files /dev/null and b/level/assets/component/decor-game.png differ diff --git a/level/assets/component/decor-wealth.png b/level/assets/component/decor-wealth.png new file mode 100644 index 0000000..479243c Binary files /dev/null and b/level/assets/component/decor-wealth.png differ diff --git a/level/assets/component/game-frame-1.png b/level/assets/component/game-frame-1.png new file mode 100644 index 0000000..b304388 Binary files /dev/null and b/level/assets/component/game-frame-1.png differ diff --git a/level/assets/component/game-frame-2.png b/level/assets/component/game-frame-2.png new file mode 100644 index 0000000..2c9ee1b Binary files /dev/null and b/level/assets/component/game-frame-2.png differ diff --git a/level/assets/component/game-frame-3.png b/level/assets/component/game-frame-3.png new file mode 100644 index 0000000..47599c1 Binary files /dev/null and b/level/assets/component/game-frame-3.png differ diff --git a/level/assets/component/game-frame-4.png b/level/assets/component/game-frame-4.png new file mode 100644 index 0000000..1ddf8c9 Binary files /dev/null and b/level/assets/component/game-frame-4.png differ diff --git a/level/assets/component/game-frame-5.png b/level/assets/component/game-frame-5.png new file mode 100644 index 0000000..17af20f Binary files /dev/null and b/level/assets/component/game-frame-5.png differ diff --git a/level/assets/component/game-icon-1.png b/level/assets/component/game-icon-1.png new file mode 100644 index 0000000..872e8d8 Binary files /dev/null and b/level/assets/component/game-icon-1.png differ diff --git a/level/assets/component/game-icon-2.png b/level/assets/component/game-icon-2.png new file mode 100644 index 0000000..b4e9b0d Binary files /dev/null and b/level/assets/component/game-icon-2.png differ diff --git a/level/assets/component/game-icon-3.png b/level/assets/component/game-icon-3.png new file mode 100644 index 0000000..fe5fe22 Binary files /dev/null and b/level/assets/component/game-icon-3.png differ diff --git a/level/assets/component/game-icon-4.png b/level/assets/component/game-icon-4.png new file mode 100644 index 0000000..4f95656 Binary files /dev/null and b/level/assets/component/game-icon-4.png differ diff --git a/level/assets/component/game-icon-5.png b/level/assets/component/game-icon-5.png new file mode 100644 index 0000000..ec0163f Binary files /dev/null and b/level/assets/component/game-icon-5.png differ diff --git a/level/assets/component/upgrade-avatar-wealth.svg b/level/assets/component/upgrade-avatar-wealth.svg new file mode 100644 index 0000000..19efb22 --- /dev/null +++ b/level/assets/component/upgrade-avatar-wealth.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/level/assets/component/upgrade-car-wealth.svg b/level/assets/component/upgrade-car-wealth.svg new file mode 100644 index 0000000..751c501 --- /dev/null +++ b/level/assets/component/upgrade-car-wealth.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/level/assets/component/upgrade-game.svg b/level/assets/component/upgrade-game.svg new file mode 100644 index 0000000..8176cb0 --- /dev/null +++ b/level/assets/component/upgrade-game.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/level/assets/component/upgrade-gift-charm.svg b/level/assets/component/upgrade-gift-charm.svg new file mode 100644 index 0000000..ef651b8 --- /dev/null +++ b/level/assets/component/upgrade-gift-charm.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/level/assets/component/upgrade-gift-wealth.svg b/level/assets/component/upgrade-gift-wealth.svg new file mode 100644 index 0000000..6fa62d6 --- /dev/null +++ b/level/assets/component/upgrade-gift-wealth.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/level/assets/component/wealth-frame-1.png b/level/assets/component/wealth-frame-1.png new file mode 100644 index 0000000..25f54f4 Binary files /dev/null and b/level/assets/component/wealth-frame-1.png differ diff --git a/level/assets/component/wealth-frame-2.png b/level/assets/component/wealth-frame-2.png new file mode 100644 index 0000000..ae0aa15 Binary files /dev/null and b/level/assets/component/wealth-frame-2.png differ diff --git a/level/assets/component/wealth-frame-3.png b/level/assets/component/wealth-frame-3.png new file mode 100644 index 0000000..77304fc Binary files /dev/null and b/level/assets/component/wealth-frame-3.png differ diff --git a/level/assets/component/wealth-frame-4.png b/level/assets/component/wealth-frame-4.png new file mode 100644 index 0000000..7d0e9c8 Binary files /dev/null and b/level/assets/component/wealth-frame-4.png differ diff --git a/level/assets/component/wealth-frame-5.png b/level/assets/component/wealth-frame-5.png new file mode 100644 index 0000000..0dc4641 Binary files /dev/null and b/level/assets/component/wealth-frame-5.png differ diff --git a/level/assets/component/wealth-icon-1.png b/level/assets/component/wealth-icon-1.png new file mode 100644 index 0000000..8b17d27 Binary files /dev/null and b/level/assets/component/wealth-icon-1.png differ diff --git a/level/assets/component/wealth-icon-2.png b/level/assets/component/wealth-icon-2.png new file mode 100644 index 0000000..500497e Binary files /dev/null and b/level/assets/component/wealth-icon-2.png differ diff --git a/level/assets/component/wealth-icon-3.png b/level/assets/component/wealth-icon-3.png new file mode 100644 index 0000000..18a804a Binary files /dev/null and b/level/assets/component/wealth-icon-3.png differ diff --git a/level/assets/component/wealth-icon-4.png b/level/assets/component/wealth-icon-4.png new file mode 100644 index 0000000..0bb88e0 Binary files /dev/null and b/level/assets/component/wealth-icon-4.png differ diff --git a/level/assets/component/wealth-icon-5.png b/level/assets/component/wealth-icon-5.png new file mode 100644 index 0000000..5fd8f17 Binary files /dev/null and b/level/assets/component/wealth-icon-5.png differ diff --git a/level/index.html b/level/index.html new file mode 100644 index 0000000..0a51dcc --- /dev/null +++ b/level/index.html @@ -0,0 +1,341 @@ + + + + + + + Level + + + + +
+
+ + +
+ + + +
+ +
+ + +
Level 0
+
+ + +
+
+
Lv.0
+
40%
+
+
Lv.1
+
You need 12,345,600 more experience points
+
+ +
+
Range
+
Icons
+
Profile Frame
+
+
+ +
How to upgrade?
+
+ + +
+ + +
+
+
+ + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0a1776e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,31 @@ +{ + "name": "hyapp-h5", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hyapp-h5", + "version": "1.0.0", + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..814c116 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "hyapp-h5", + "version": "1.0.0", + "description": "H5 project", + "scripts": { + "format": "prettier --write \"**/*.{js,html,css,json,md}\"", + "format:check": "prettier --check \"**/*.{js,html,css,json,md}\"" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/rank/index.html b/rank/index.html index 40a2682..4ebeaf3 100644 --- a/rank/index.html +++ b/rank/index.html @@ -1,481 +1,1423 @@ - - - - Ranking - - - -
-
- - -
- -
-
-
-
-
+ + + + Ranking + + + +
+
+ + +
+ +
+
+
+
+
-
- -
- -
Note
-
-
- - - -
-
-
-
Ranking Rewards
-
- - - -
-
-
-
-
- -
+
+ +
+ +
Note
+
+
+ + + +
+
+
+
Ranking Rewards
+
+ + + +
+
+
+
+
+ +
- - - - + + + - + function byId(id) { + return document.getElementById(id); + } + function queryValue(name) { + return new URLSearchParams(window.location.search).get( + name + ); + } + function mockEnabled() { + return queryValue('mock') === '1'; + } + function money(value) { + var num = Number(value || 0); + if (num >= 1000000) + return ( + (num / 1000000).toFixed( + num % 1000000 === 0 ? 0 : 1 + ) + 'm' + ); + if (num >= 1000) + return ( + (num / 1000).toFixed(num % 1000 === 0 ? 0 : 1) + 'k' + ); + return String(num); + } + function userName(item) { + return ( + (item.user && + (item.user.username || + item.user.display_user_id || + item.user.user_id)) || + (item.room && + (item.room.title || + item.room.room_short_id || + item.room.room_id)) || + item.room_id || + item.user_id || + 'Namenamename' + ); + } + function avatar(item, size) { + if (item.user && item.user.avatar) return item.user.avatar; + if (item.room && item.room.cover_url) + return item.room.cover_url; + if (size === 'large') + return 'assets/default-avatar-large.png'; + if (size === 'mid') return 'assets/default-avatar-mid.png'; + return 'assets/default-avatar-small.png'; + } + function sampleItems() { + return sampleNames.map(function (name, index) { + var rank = index + 1; + var base = rank <= 3 ? 123400 : 102400; + if (state.board === 'room') { + return { + rank: rank, + room_id: 'room_' + rank, + gift_value: base, + room: { room_id: 'room_' + rank, title: name }, + }; + } + return { + rank: rank, + user_id: String(10000 + rank), + gift_value: base, + user: { + user_id: String(10000 + rank), + username: name, + }, + }; + }); + } + function badges() { + var html = ''; + for (var i = 0; i < 4; i += 1) + html += ''; + return html; + } + function score(value, className) { + return ( + '
' + + money(value) + + '
' + ); + } + function normalizeItem(raw) { + var item = raw || {}; + var user = item.user || {}; + var room = item.room || {}; + var userId = item.user_id || user.user_id || ''; + var roomId = item.room_id || room.room_id || ''; + return { + rank: Number(item.rank || 0), + user_id: String(userId || ''), + room_id: String(roomId || ''), + gift_value: Number( + item.gift_value || item.giftValue || item.value || 0 + ), + gift_count: Number( + item.gift_count || item.giftCount || 0 + ), + transaction_count: Number( + item.transaction_count || item.transactionCount || 0 + ), + last_gift_at_ms: Number( + item.last_gift_at_ms || item.lastGiftAtMs || 0 + ), + user: userId + ? { + user_id: String(userId), + display_user_id: + user.display_user_id || + user.displayUserId || + '', + username: + user.username || + user.nickname || + user.name || + '', + avatar: + user.avatar || + user.avatar_url || + user.avatarUrl || + '', + } + : null, + room: roomId + ? { + room_id: String(roomId), + room_short_id: + room.room_short_id || + room.roomShortId || + '', + title: room.title || room.name || '', + cover_url: + room.cover_url || + room.coverUrl || + room.room_avatar || + room.roomAvatar || + room.avatar || + '', + } + : null, + }; + } + function normalizePayload(data, reset) { + var items = ( + data && Array.isArray(data.items) ? data.items : [] + ) + .map(normalizeItem) + .filter(function (item) { + return item.rank > 0; + }); + state.total = Number( + (data && data.total) || items.length || 0 + ); + state.page = Number((data && data.page) || state.page); + state.pageSize = Number( + (data && (data.page_size || data.pageSize)) || + state.pageSize + ); + state.myRank = + data && data.my_rank + ? normalizeItem(data.my_rank) + : null; + state.items = reset ? items : state.items.concat(items); + state.loaded = true; + state.error = null; + } + function visibleItems() { + if (state.items.length) return state.items; + return mockEnabled() ? sampleItems() : []; + } + function renderPodium() { + var items = visibleItems(); + var top = [items[0], items[1], items[2]]; + var html = ''; + if (top[0]) { + html += + ''; + } + if (top[1]) { + html += + ''; + } + if (top[2]) { + html += + ''; + } + byId('podium').innerHTML = html; + } + function row(item, mine) { + if (!item) return ''; + return ( + '' + ); + } + function renderList() { + var items = visibleItems(); + if (!items.length) { + byId('rankList').innerHTML = + '
' + + (state.error + ? 'Network error' + : state.loading || !state.loaded + ? 'Loading' + : 'No ranking data') + + '
'; + byId('myRank').innerHTML = ''; + return; + } + byId('rankList').innerHTML = + items + .slice(3) + .map(function (item) { + return row(item); + }) + .join('') + '
'; + byId('myRank').innerHTML = row( + state.myRank || items[3] || items[0] + ); + } + function applyTheme() { + var cfg = ASSETS[state.board]; + document.documentElement.style.setProperty( + '--page-bg', + cfg.page + ); + document.documentElement.style.setProperty( + '--shade', + cfg.shade + ); + document.documentElement.style.setProperty( + '--row-bg', + cfg.row + ); + document.documentElement.style.setProperty( + '--score-bg', + cfg.score + ); + document.documentElement.style.setProperty( + '--score-bg-soft', + cfg.score.replace(')', ', .72)').replace('rgb', 'rgba') + ); + document.documentElement.style.setProperty( + '--mine-a', + cfg.mineA + ); + document.documentElement.style.setProperty( + '--mine-b', + cfg.mineB + ); + document.documentElement.style.setProperty( + '--bg-top', + cfg.bgTop + ); + document.documentElement.style.setProperty( + '--bg-filter', + cfg.bgFilter || 'none' + ); + byId('rankBg').src = cfg.bg; + byId('rankBgCharm').hidden = !cfg.bg2; + if (cfg.bg2) byId('rankBgCharm').src = cfg.bg2; + } + function syncTabs() { + document + .querySelectorAll('[data-board]') + .forEach(function (node) { + node.classList.toggle( + 'active', + node.dataset.board === state.board + ); + }); + document + .querySelectorAll('[data-period]') + .forEach(function (node) { + node.classList.toggle( + 'active', + node.dataset.period === state.period + ); + }); + } + function activePage() { + return byId('notePage').classList.contains('active') + ? byId('notePage') + : byId('rankPage'); + } + function resizeApp() { + var designWidth = 375; + var scale = Math.min(window.innerWidth / designWidth, 1.25); + var page = activePage(); + var height = Math.max(812, page.scrollHeight); + document.documentElement.style.setProperty( + '--app-scale', + String(scale) + ); + byId('appViewport').style.height = height + 'px'; + document.body.style.minHeight = + Math.ceil(height * scale) + 'px'; + } + function scheduleResize() { + window.requestAnimationFrame(resizeApp); + } + function syncLoading() { + var overlay = byId('loadingOverlay'); + overlay.classList.toggle('active', state.loading); + overlay.setAttribute( + 'aria-hidden', + state.loading ? 'false' : 'true' + ); + } + function render() { + applyTheme(); + syncTabs(); + syncLoading(); + renderPodium(); + renderList(); + scheduleResize(); + } + function load(reset) { + if (state.loading) return Promise.resolve(); + if (reset) { + state.page = 1; + state.items = []; + state.total = 0; + state.myRank = null; + state.loaded = false; + state.error = null; + render(); + } + if (mockEnabled()) { + state.loaded = true; + state.error = null; + return Promise.resolve(render()); + } + if (!window.HyAppAPI || !window.HyAppAPI.getAccessToken()) { + state.loaded = true; + state.error = new Error('missing_token'); + return Promise.resolve(render()); + } + state.loading = true; + state.error = null; + render(); + return window.HyAppAPI.get( + '/api/v1/activities/user-leaderboards', + { + board_type: state.board, + period: state.period, + page: state.page, + page_size: state.pageSize, + } + ) + .then(function (data) { + normalizePayload(data, reset); + render(); + }) + .catch(function (error) { + state.loaded = true; + state.error = error; + render(); + }) + .finally(function () { + state.loading = false; + render(); + }); + } + function openItem(item) { + if (!item) return; + if (state.board === 'room') { + window.HyAppBridge.openRoom( + item.room_id || (item.room && item.room.room_id) + ); + } else { + window.HyAppBridge.openUser( + item.user_id || (item.user && item.user.user_id) + ); + } + } + function showNote() { + byId('rankPage').classList.add('hidden'); + byId('notePage').classList.add('active'); + byId('noteCopy').textContent = noteCopy[state.board]; + renderRewards(); + scheduleResize(); + window.HyAppBridge.openRankNote(state.board); + } + function hideNote() { + byId('notePage').classList.remove('active'); + byId('rankPage').classList.remove('hidden'); + scheduleResize(); + } + function renderRewards() { + byId('noteCopy').textContent = noteCopy[state.board]; + byId('rewardGrid').innerHTML = [1, 2, 3, 4] + .map(function () { + return '

Top1

Namename
10000/7days
'; + }) + .join(''); + scheduleResize(); + } + byId('boardTabs').addEventListener('click', function (event) { + var target = event.target.closest('[data-board]'); + if (!target || target.dataset.board === state.board) return; + state.board = target.dataset.board; + load(true); + }); + byId('noteTabs').addEventListener('click', function (event) { + var target = event.target.closest('[data-board]'); + if (!target) return; + state.board = target.dataset.board; + syncTabs(); + renderRewards(); + }); + byId('periodTabs').addEventListener('click', function (event) { + var target = event.target.closest('[data-period]'); + if (!target || target.dataset.period === state.period) + return; + state.period = target.dataset.period; + load(true); + }); + byId('rewardPeriods').addEventListener( + 'click', + function (event) { + var target = event.target.closest('[data-period]'); + if (!target) return; + state.period = target.dataset.period; + syncTabs(); + } + ); + byId('podium').addEventListener('click', function (event) { + var target = event.target.closest('[data-index]'); + if (target) + openItem(visibleItems()[Number(target.dataset.index)]); + }); + byId('rankList').addEventListener('click', function (event) { + var target = event.target.closest('[data-rank]'); + if (!target) return; + var item = visibleItems().find(function (candidate) { + return String(candidate.rank) === target.dataset.rank; + }); + openItem(item); + }); + byId('myRank').addEventListener('click', function () { + openItem(state.myRank); + }); + byId('backButton').addEventListener('click', function () { + window.HyAppBridge.back(); + }); + byId('helpButton').addEventListener('click', showNote); + byId('noteBackButton').addEventListener('click', hideNote); + window.addEventListener('scroll', function () { + if ( + !state.total || + state.items.length >= state.total || + state.loading + ) + return; + if ( + window.innerHeight + window.scrollY > + document.body.scrollHeight - 180 + ) { + state.page += 1; + load(false); + } + }); + window.addEventListener('resize', resizeApp); + window.addEventListener('orientationchange', scheduleResize); + render(); + window.HyAppBridge.ready({ page: 'rank' }); + load(true).finally(function () { + window.HyAppParams.loadUser().catch(function () { + return null; + }); + }); + })(); + + diff --git a/vip/assets/avatar-frame.png b/vip/assets/avatar-frame.png new file mode 100644 index 0000000..e710114 Binary files /dev/null and b/vip/assets/avatar-frame.png differ diff --git a/vip/assets/back.png b/vip/assets/back.png new file mode 100644 index 0000000..d2935f3 --- /dev/null +++ b/vip/assets/back.png @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/vip/assets/badge-vip7.png b/vip/assets/badge-vip7.png new file mode 100644 index 0000000..7e33063 Binary files /dev/null and b/vip/assets/badge-vip7.png differ diff --git a/vip/assets/bg-meteor.png b/vip/assets/bg-meteor.png new file mode 100644 index 0000000..56828bf Binary files /dev/null and b/vip/assets/bg-meteor.png differ diff --git a/vip/assets/bg-palace.png b/vip/assets/bg-palace.png new file mode 100644 index 0000000..6101bc1 Binary files /dev/null and b/vip/assets/bg-palace.png differ diff --git a/vip/assets/bg-starry.png b/vip/assets/bg-starry.png new file mode 100644 index 0000000..0b0c772 Binary files /dev/null and b/vip/assets/bg-starry.png differ diff --git a/vip/assets/bottom-texture.png b/vip/assets/bottom-texture.png new file mode 100644 index 0000000..f1e2feb Binary files /dev/null and b/vip/assets/bottom-texture.png differ diff --git a/vip/assets/button-texture.png b/vip/assets/button-texture.png new file mode 100644 index 0000000..4591774 Binary files /dev/null and b/vip/assets/button-texture.png differ diff --git a/vip/assets/coin.png b/vip/assets/coin.png new file mode 100644 index 0000000..f61a28e Binary files /dev/null and b/vip/assets/coin.png differ diff --git a/vip/assets/corner.png b/vip/assets/corner.png new file mode 100644 index 0000000..cec3d03 Binary files /dev/null and b/vip/assets/corner.png differ diff --git a/vip/assets/divider.png b/vip/assets/divider.png new file mode 100644 index 0000000..8dbb70a Binary files /dev/null and b/vip/assets/divider.png differ diff --git a/vip/assets/entry-effect.png b/vip/assets/entry-effect.png new file mode 100644 index 0000000..c90eb4c Binary files /dev/null and b/vip/assets/entry-effect.png differ diff --git a/vip/assets/entry-frame.png b/vip/assets/entry-frame.png new file mode 100644 index 0000000..f477263 Binary files /dev/null and b/vip/assets/entry-frame.png differ diff --git a/vip/assets/figma-vip-content-no-status.png b/vip/assets/figma-vip-content-no-status.png new file mode 100644 index 0000000..26b0d0f Binary files /dev/null and b/vip/assets/figma-vip-content-no-status.png differ diff --git a/vip/assets/figma-vip-full.png b/vip/assets/figma-vip-full.png new file mode 100644 index 0000000..db0c45f Binary files /dev/null and b/vip/assets/figma-vip-full.png differ diff --git a/vip/assets/figma-vip-no-status.png b/vip/assets/figma-vip-no-status.png new file mode 100644 index 0000000..d5aa3af Binary files /dev/null and b/vip/assets/figma-vip-no-status.png differ diff --git a/vip/assets/home-indicator.png b/vip/assets/home-indicator.png new file mode 100644 index 0000000..c9a37a8 --- /dev/null +++ b/vip/assets/home-indicator.png @@ -0,0 +1,7 @@ + + + + + + + diff --git a/vip/assets/icon-id.png b/vip/assets/icon-id.png new file mode 100644 index 0000000..4319930 --- /dev/null +++ b/vip/assets/icon-id.png @@ -0,0 +1,3 @@ + + + diff --git a/vip/assets/icon-nickname.png b/vip/assets/icon-nickname.png new file mode 100644 index 0000000..b5fef6a --- /dev/null +++ b/vip/assets/icon-nickname.png @@ -0,0 +1,3 @@ + + + diff --git a/vip/assets/icon-profile.png b/vip/assets/icon-profile.png new file mode 100644 index 0000000..426b2fb --- /dev/null +++ b/vip/assets/icon-profile.png @@ -0,0 +1,3 @@ + + + diff --git a/vip/assets/icon-room.png b/vip/assets/icon-room.png new file mode 100644 index 0000000..9c5f519 --- /dev/null +++ b/vip/assets/icon-room.png @@ -0,0 +1,3 @@ + + + diff --git a/vip/assets/mic-animation.png b/vip/assets/mic-animation.png new file mode 100644 index 0000000..5e18406 Binary files /dev/null and b/vip/assets/mic-animation.png differ diff --git a/vip/assets/mount.png b/vip/assets/mount.png new file mode 100644 index 0000000..0073ef0 Binary files /dev/null and b/vip/assets/mount.png differ diff --git a/vip/assets/privilege-circle.png b/vip/assets/privilege-circle.png new file mode 100644 index 0000000..7706049 --- /dev/null +++ b/vip/assets/privilege-circle.png @@ -0,0 +1,3 @@ + + + diff --git a/vip/assets/privilege-frame.png b/vip/assets/privilege-frame.png new file mode 100644 index 0000000..84fb263 Binary files /dev/null and b/vip/assets/privilege-frame.png differ diff --git a/vip/assets/title-vip.png b/vip/assets/title-vip.png new file mode 100644 index 0000000..b7f99fd Binary files /dev/null and b/vip/assets/title-vip.png differ diff --git a/vip/index.html b/vip/index.html new file mode 100644 index 0000000..a659ab4 --- /dev/null +++ b/vip/index.html @@ -0,0 +1,1035 @@ + + + + + + VIP + + + +
+
+ + + + + + + +
+ + Decoration Privileges + +
+ +
+ + +
SVIP Badge
+
+
+ + +
Frame
+
+
+ + +
Mount
+
+
+ + +
Mic Animation
+
+
+ + +
Entry Effect
+
+ +
+ + VIP Privileges + +
+ +
+
+
+ + +
+
Gif Profile
Picture
+
+
+
+ + +
+
Colorful
Nickname
+
+
+
+ + +
+
Gif Room
Picture
+
+
+
+ +
ID
+
+
Colorful ID
+
+
+ + +
+
+ +
+
+ + 30.5 M / 30 Days +
+ +
+
+
+ + + + + + +