feat: add Fami and Huwaa H5 experiences
7
.gitignore
vendored
@ -1 +1,6 @@
|
||||
node_modules/
|
||||
node_modules/
|
||||
.waylog/
|
||||
**/.audit/
|
||||
.DS_Store
|
||||
design-qa.md
|
||||
**/design-qa.md
|
||||
|
||||
@ -11,3 +11,11 @@ H5 多语言能力放在 common/i18n.js 和 common/locales/\*.json,当前面
|
||||
所有逻辑功能配置默认打开;只有密钥、地址、三方账号等必须由环境提供的敏感值使用占位符或环境配置,不在默认配置里伪造可用凭证。
|
||||
|
||||
所有业务逻辑、状态流转、权限判断、支付链路、异步流程都保持高密度逻辑注释,注释解释为什么这样处理和失败分支,不复述语法。
|
||||
|
||||
## Figma / 截图还原超级红线
|
||||
|
||||
- 全局永久禁止把整页截图、Figma 根 Frame、整段轮播、整块底栏或包含多个可独立元素的大区块直接作为页面图片实现。
|
||||
- 设计稿必须拆成独立视觉图层;文字、按钮、状态、列表、动态媒体与交互区域必须使用 DOM、CSS 和真实接口数据实现。
|
||||
- 导出的设计素材必须能追溯到具体的单一视觉节点,并记录 source node、用途、尺寸和哈希;禁止素材内嵌动态文字、控件或业务媒体。
|
||||
- 允许使用不包含文字、控件和动态业务内容的纯背景层,但它不能替代其上的任何可拆元素。
|
||||
- 验收必须按图层、元素边界、间距、样式和交互逐项核对;禁止用整页设计截图覆盖页面,也禁止把整页截图像素差分作为通过依据。
|
||||
|
||||
267
activity/cp-space/fami.html
Normal file
@ -0,0 +1,267 @@
|
||||
<!doctype html>
|
||||
<html lang="en" dir="ltr" data-hy-app-code="fami">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<title data-i18n="cpSpace.pageTitle">Best Friend</title>
|
||||
<link rel="icon" href="data:," />
|
||||
<link
|
||||
rel="preload"
|
||||
href="./fami/assets/fonts/source-han-sans-sc-medium-latin.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
<link rel="stylesheet" href="../../common/theme.css?v=20260716" />
|
||||
<link rel="stylesheet" href="./fami/fami.css?v=20260716" />
|
||||
</head>
|
||||
<body>
|
||||
<main
|
||||
class="fami-page"
|
||||
id="famiPage"
|
||||
data-tab="cp"
|
||||
aria-label="Best Friend space"
|
||||
data-i18n-aria="cpSpace.pageLabel"
|
||||
>
|
||||
<section class="fami-stage" id="famiStage">
|
||||
<div class="fami-design" id="famiDesign">
|
||||
<img
|
||||
class="fami-background"
|
||||
id="famiBackground"
|
||||
src="./fami/assets/layers/bg-cp.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
<section class="fami-status-bar" aria-hidden="true">
|
||||
<span class="fami-status-time">9:41</span>
|
||||
<img
|
||||
class="fami-status-cellular"
|
||||
src="./fami/assets/layers/cellular.svg"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="fami-status-wifi"
|
||||
src="./fami/assets/layers/wifi.svg"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="fami-status-battery"
|
||||
src="./fami/assets/layers/battery.svg"
|
||||
alt=""
|
||||
/>
|
||||
</section>
|
||||
|
||||
<header
|
||||
class="fami-nav"
|
||||
aria-label="Best Friend navigation"
|
||||
data-i18n-aria="cpSpace.navLabel"
|
||||
>
|
||||
<button
|
||||
class="fami-nav-button fami-nav-button--back"
|
||||
id="famiBackButton"
|
||||
type="button"
|
||||
aria-label="Back"
|
||||
data-i18n-aria="cpSpace.back"
|
||||
>
|
||||
<img src="./fami/assets/layers/back.svg" alt="" />
|
||||
</button>
|
||||
<h1 data-i18n="cpSpace.title">Best Friend</h1>
|
||||
<button
|
||||
class="fami-nav-button fami-nav-button--help"
|
||||
id="famiHelpButton"
|
||||
type="button"
|
||||
aria-label="Help"
|
||||
data-i18n-aria="cpSpace.help"
|
||||
>
|
||||
<img src="./fami/assets/layers/help.svg" alt="" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<nav
|
||||
class="fami-tabs"
|
||||
role="tablist"
|
||||
aria-label="Best Friend tabs"
|
||||
data-i18n-aria="cpSpace.tabsLabel"
|
||||
>
|
||||
<img
|
||||
class="fami-tab-jewel"
|
||||
id="famiTabJewel"
|
||||
src="./fami/assets/layers/tab-cp.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<button
|
||||
id="famiTabCp"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="famiContent"
|
||||
data-tab="cp"
|
||||
>
|
||||
<span data-i18n="cpSpace.tabCp">CP</span>
|
||||
</button>
|
||||
<button
|
||||
id="famiTabBrother"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="famiContent"
|
||||
data-tab="brother"
|
||||
>
|
||||
<span data-i18n="cpSpace.tabBrother">Brother</span>
|
||||
</button>
|
||||
<button
|
||||
id="famiTabSister"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="famiContent"
|
||||
data-tab="sister"
|
||||
>
|
||||
<span data-i18n="cpSpace.tabSister">Sister</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<section
|
||||
class="fami-content"
|
||||
id="famiContent"
|
||||
role="tabpanel"
|
||||
aria-labelledby="famiTabCp"
|
||||
aria-live="polite"
|
||||
aria-busy="true"
|
||||
>
|
||||
<div
|
||||
class="fami-relation-list"
|
||||
id="famiRelationList"
|
||||
></div>
|
||||
<button
|
||||
class="fami-state"
|
||||
id="famiStateButton"
|
||||
type="button"
|
||||
hidden
|
||||
></button>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<template id="famiRelationTemplate">
|
||||
<article class="fami-relation-card">
|
||||
<img
|
||||
class="fami-card-skin"
|
||||
data-layer="card"
|
||||
src="./fami/assets/layers/card-cp.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
<figure class="fami-avatar fami-avatar--left">
|
||||
<span class="fami-avatar-photo" data-avatar="me"></span>
|
||||
<img
|
||||
class="fami-avatar-frame"
|
||||
src="./fami/assets/layers/avatar-frame.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<figure class="fami-avatar fami-avatar--right">
|
||||
<span
|
||||
class="fami-avatar-photo"
|
||||
data-avatar="partner"
|
||||
></span>
|
||||
<img
|
||||
class="fami-avatar-frame"
|
||||
src="./fami/assets/layers/avatar-frame.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<div class="fami-center-art-wrap">
|
||||
<img
|
||||
class="fami-center-art"
|
||||
data-layer="center"
|
||||
src="./fami/assets/layers/center-cp.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<strong class="fami-days-value" data-value="days">0</strong>
|
||||
<span class="fami-days-unit" data-i18n="cpSpace.daysUnit"
|
||||
>Days</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="fami-exp-pill">
|
||||
<span class="fami-exp-pill-skin fami-exp-pill-skin--cp">
|
||||
<img
|
||||
src="./fami/assets/layers/pill-cp-left.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="./fami/assets/layers/pill-cp-center.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="./fami/assets/layers/pill-cp-right.png"
|
||||
alt=""
|
||||
/>
|
||||
</span>
|
||||
<img
|
||||
class="fami-exp-pill-skin fami-exp-pill-skin--brother"
|
||||
src="./fami/assets/layers/pill-brother.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="fami-exp-pill-skin fami-exp-pill-skin--sister"
|
||||
src="./fami/assets/layers/pill-sister.png"
|
||||
alt=""
|
||||
/>
|
||||
<span data-value="exp-away"></span>
|
||||
</div>
|
||||
|
||||
<section class="fami-progress-block">
|
||||
<div class="fami-level-badge fami-level-badge--left">
|
||||
<img
|
||||
data-layer="level"
|
||||
src="./fami/assets/layers/level-cp.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<span data-value="level-left"></span>
|
||||
</div>
|
||||
<div class="fami-progress-track">
|
||||
<span
|
||||
class="fami-progress-fill"
|
||||
data-value="progress"
|
||||
></span>
|
||||
<strong
|
||||
class="fami-progress-text"
|
||||
data-value="progress-text"
|
||||
>0/0</strong
|
||||
>
|
||||
</div>
|
||||
<div class="fami-level-badge fami-level-badge--right">
|
||||
<img
|
||||
data-layer="level"
|
||||
src="./fami/assets/layers/level-cp.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<span data-value="level-right"></span>
|
||||
</div>
|
||||
<p class="fami-time-text" data-value="time"></p>
|
||||
</section>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script src="./fami/product.js?v=20260716"></script>
|
||||
<script src="../../common/params.js?v=20260716"></script>
|
||||
<script src="../../common/i18n.js?v=20260716"></script>
|
||||
<script src="../../common/api.js?v=20260716"></script>
|
||||
<script src="../../common/jsbridge.js?v=20260716"></script>
|
||||
<script src="../../common/toast.js?v=20260716"></script>
|
||||
<script src="./fami/fami.js?v=20260716"></script>
|
||||
</body>
|
||||
</html>
|
||||
96
activity/cp-space/fami/assets/fonts/OFL.txt
Normal file
@ -0,0 +1,96 @@
|
||||
Copyright 2014-2025 Adobe (http://www.adobe.com/), with Reserved Font
|
||||
Name 'Source'. Source is a trademark of Adobe in the United States
|
||||
and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
activity/cp-space/fami/assets/layers/avatar-frame.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
9
activity/cp-space/fami/assets/layers/back.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_20_返回">
|
||||
<g id="Vector">
|
||||
</g>
|
||||
<g id="Vector_2">
|
||||
</g>
|
||||
<path id="Vector_3" d="M16 4L8 12L16 20" stroke="var(--stroke-0, white)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 419 B |
7
activity/cp-space/fami/assets/layers/battery.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 24.3307 11.3333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Battery">
|
||||
<path id="Border" opacity="0.35" d="M2.66699 0.5H19.333C20.5296 0.5 21.5 1.47038 21.5 2.66699V8.66699C21.4998 9.86346 20.5295 10.833 19.333 10.833H2.66699C1.4705 10.833 0.500175 9.86346 0.5 8.66699V2.66699C0.5 1.47038 1.47039 0.5 2.66699 0.5Z" stroke="var(--stroke-0, white)"/>
|
||||
<path id="Cap" opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M23.0027 7.66213C23.8074 7.32336 24.3307 6.53526 24.3307 5.66213C24.3307 4.789 23.8074 4.00091 23.0027 3.66213V7.66213V7.66213Z" fill="var(--fill-0, white)"/>
|
||||
<path id="Capacity" fill-rule="evenodd" clip-rule="evenodd" d="M18.666 1.99872C19.4024 1.99872 19.9993 2.59567 19.9993 3.33205V7.99872C19.9993 8.7351 19.4024 9.33205 18.666 9.33205H3.33268C2.5963 9.33205 1.99935 8.7351 1.99935 7.99872V3.33205C1.99935 2.59567 2.5963 1.99872 3.33268 1.99872H18.666V1.99872Z" fill="var(--fill-0, white)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
activity/cp-space/fami/assets/layers/bg-brother.png
Normal file
|
After Width: | Height: | Size: 677 KiB |
BIN
activity/cp-space/fami/assets/layers/bg-cp.png
Normal file
|
After Width: | Height: | Size: 670 KiB |
BIN
activity/cp-space/fami/assets/layers/bg-sister.png
Normal file
|
After Width: | Height: | Size: 702 KiB |
BIN
activity/cp-space/fami/assets/layers/card-brother.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
activity/cp-space/fami/assets/layers/card-cp.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
activity/cp-space/fami/assets/layers/card-sister.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
3
activity/cp-space/fami/assets/layers/cellular.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 17.0001 11.3317" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Cellular Connection" fill-rule="evenodd" clip-rule="evenodd" d="M11.331 2.99835C11.8832 2.99835 12.331 3.44607 12.331 3.99835V10.3317C12.331 10.884 11.8832 11.3317 11.331 11.3317H10.331C9.77867 11.3317 9.33096 10.884 9.33096 10.3317V3.99835C9.33096 3.44607 9.77867 2.99835 10.331 2.99835H11.331V2.99835ZM16.0001 0C16.5524 0 17.0001 0.447712 17.0001 1V9.66667C17.0001 10.219 16.5524 10.6667 16.0001 10.6667H15.0001C14.4479 10.6667 14.0001 10.219 14.0001 9.66667V1C14.0001 0.447712 14.4479 0 15.0001 0V0H16.0001V0ZM2 7.00378C2.55229 7.00378 3 7.4515 3 8.00378V10.0038C3 10.5561 2.55229 11.0038 2 11.0038H0.999999C0.447714 11.0038 0 10.5561 0 10.0038V8.00378C0 7.4515 0.447714 7.00378 0.999999 7.00378H2V7.00378ZM6.66177 4.99767C7.21405 4.99767 7.66177 5.44538 7.66177 5.99767V9.99766C7.66177 10.55 7.21405 10.9977 6.66177 10.9977H5.66177C5.10948 10.9977 4.66177 10.55 4.66177 9.99766V5.99767C4.66177 5.44538 5.10948 4.99767 5.66177 4.99767H6.66177V4.99767Z" fill="var(--fill-0, white)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
BIN
activity/cp-space/fami/assets/layers/center-brother.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
activity/cp-space/fami/assets/layers/center-cp.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
activity/cp-space/fami/assets/layers/center-sister.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
5
activity/cp-space/fami/assets/layers/help.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 25.4 25.4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="help">
|
||||
<path id="Vector" d="M12.7 19.3667V19.38M12.7 14.7C12.6755 14.2672 12.7922 13.8381 13.0327 13.4774C13.2732 13.1166 13.6244 12.8438 14.0333 12.7C14.5345 12.5084 14.9843 12.203 15.3474 11.808C15.7105 11.4129 15.977 10.939 16.1258 10.4235C16.2746 9.90796 16.3017 9.36494 16.205 8.83717C16.1083 8.3094 15.8904 7.81128 15.5684 7.38204C15.2465 6.95279 14.8293 6.60413 14.3497 6.36351C13.8701 6.12289 13.3413 5.99687 12.8047 5.99538C12.2681 5.99389 11.7386 6.11697 11.2577 6.35493C10.7768 6.59289 10.3576 6.93923 10.0333 7.36668M0.7 12.7C0.7 14.2759 1.01039 15.8363 1.61345 17.2922C2.2165 18.7481 3.10042 20.071 4.21472 21.1853C5.32902 22.2996 6.65189 23.1835 8.1078 23.7866C9.56371 24.3896 11.1241 24.7 12.7 24.7C14.2759 24.7 15.8363 24.3896 17.2922 23.7866C18.7481 23.1835 20.071 22.2996 21.1853 21.1853C22.2996 20.071 23.1835 18.7481 23.7866 17.2922C24.3896 15.8363 24.7 14.2759 24.7 12.7C24.7 11.1241 24.3896 9.56371 23.7866 8.1078C23.1835 6.65189 22.2996 5.32902 21.1853 4.21472C20.071 3.10042 18.7481 2.2165 17.2922 1.61345C15.8363 1.01039 14.2759 0.7 12.7 0.7C11.1241 0.7 9.56371 1.01039 8.1078 1.61345C6.65189 2.2165 5.32902 3.10042 4.21472 4.21472C3.10042 5.32902 2.2165 6.65189 1.61345 8.1078C1.01039 9.56371 0.7 11.1241 0.7 12.7Z" stroke="var(--stroke-0, white)" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
activity/cp-space/fami/assets/layers/level-brother.png
Normal file
|
After Width: | Height: | Size: 1017 B |
BIN
activity/cp-space/fami/assets/layers/level-cp.png
Normal file
|
After Width: | Height: | Size: 1016 B |
BIN
activity/cp-space/fami/assets/layers/level-sister.png
Normal file
|
After Width: | Height: | Size: 967 B |
464
activity/cp-space/fami/assets/layers/manifest.json
Normal file
@ -0,0 +1,464 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"rule": "Layered restoration only. Root frames, full-page screenshots, dynamic text, controls, and production avatars are not implementation assets.",
|
||||
"figma": {
|
||||
"fileKey": "vaDp5wwn6AOQVlt9zk0f9r",
|
||||
"page": {
|
||||
"id": "1:27538",
|
||||
"name": "yumi"
|
||||
},
|
||||
"entryFrames": {
|
||||
"cp": "859:2027",
|
||||
"brother": "859:2247",
|
||||
"sister": "859:2454"
|
||||
}
|
||||
},
|
||||
"baseFrame": {
|
||||
"width": 375,
|
||||
"height": 812
|
||||
},
|
||||
"assets": [
|
||||
{
|
||||
"file": "bg-cp.png",
|
||||
"sourceNode": "859:2028",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Mask group",
|
||||
"sourceType": "frame",
|
||||
"purpose": "CP pure background layer",
|
||||
"bounds": { "x": 0, "y": 0, "width": 375, "height": 812 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 685603,
|
||||
"sha256": "a16b0d609a0485653e866b6f6cdaa22a8e22cf0d4bd2edd6c738d6e1b8e82747",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "bg-brother.png",
|
||||
"sourceNode": "859:2248",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Mask group",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Brother pure background layer",
|
||||
"bounds": { "x": 0, "y": 0, "width": 375, "height": 812 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 693082,
|
||||
"sha256": "866c6b111bd91894dab45d23a96493cb83fb526e282ce9ce26a4f243f5896c5e",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "bg-sister.png",
|
||||
"sourceNode": "859:2455",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Mask group",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Sister pure background layer",
|
||||
"bounds": { "x": 0, "y": 0, "width": 375, "height": 812 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 718426,
|
||||
"sha256": "701a6735476e4fe44c2c01a94968dc6704c94c72171f788bdf9be398dacbd554",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "tab-cp.png",
|
||||
"sourceNode": "859:2059",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2090056311",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Selected CP tab jewel without label text",
|
||||
"bounds": { "x": 9, "y": 107, "width": 130, "height": 48 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 12416,
|
||||
"sha256": "2f024464ea3b42311732118312817f92af4558e2abf73331b96951515f961de0",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "tab-brother.png",
|
||||
"sourceNode": "859:2279",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2090056311",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Selected Brother tab jewel without label text",
|
||||
"bounds": { "x": 126, "y": 107, "width": 130, "height": 48 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 12330,
|
||||
"sha256": "eb092fc233d2496860f8be984bb259da4c32357703edd3f4dec51517ea7ba66c",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "tab-sister.png",
|
||||
"sourceNode": "859:2486",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2090056343",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Selected Sister tab jewel without label text",
|
||||
"bounds": { "x": 236, "y": 107, "width": 130, "height": 48 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 11938,
|
||||
"sha256": "ed44cc60056760e216131dda2b932783a315de9c6ba88992fbafb6d210948f76",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "card-cp.png",
|
||||
"sourceNode": "859:2065",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2090056340",
|
||||
"sourceType": "frame",
|
||||
"purpose": "CP decorative card skin without relationship data",
|
||||
"bounds": { "x": 14, "y": 169, "width": 347, "height": 199 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 117918,
|
||||
"sha256": "4f78f7349e7bdd2d501d7fd017a1fe955323d6d9d316b818f4d72b1645c84929",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "card-brother.png",
|
||||
"sourceNode": "859:2287",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2090056325",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Brother decorative card skin without relationship data",
|
||||
"bounds": { "x": 14, "y": 169, "width": 347, "height": 199 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 123692,
|
||||
"sha256": "81e6bbf780fbccb7796c160d4e8e6596bed2af6cfec0a2b378f8ebf1fd466e9b",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "card-sister.png",
|
||||
"sourceNode": "859:2494",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2090056336",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Sister decorative card skin without relationship data",
|
||||
"bounds": { "x": 14, "y": 169, "width": 347, "height": 199 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 120470,
|
||||
"sha256": "803644ee833188033a2c4f7a2b4f051228ca7138eb0d478e426a24056a461653",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "center-cp.png",
|
||||
"sourceNode": "859:2189",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "image 4790",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "CP center heart ornament without days or EXP text",
|
||||
"bounds": { "x": 120, "y": 175, "width": 136, "height": 120 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 20823,
|
||||
"sha256": "5041a01b2856d7f28843601652da21791d47b81a6d0a22b08a80f83c487c5520",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "center-brother.png",
|
||||
"sourceNode": "859:2409",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "image 4799",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Brother center shield ornament without days or EXP text",
|
||||
"bounds": { "x": 120, "y": 175, "width": 136, "height": 136 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 18728,
|
||||
"sha256": "1765558831f54c2404464d5ae907ddf0011d094eecb8c927b5be47af53d8ee62",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "center-sister.png",
|
||||
"sourceNode": "859:2618",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "image 4796",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Sister floral ornament without days or EXP text",
|
||||
"bounds": { "x": 120, "y": 175, "width": 136, "height": 136 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 34323,
|
||||
"sha256": "eccd16b84a61a262953eac81cd5b511256bfb595c9c3a130785c5861dfb2fd43",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "avatar-frame.png",
|
||||
"sourceNode": "859:2183",
|
||||
"equivalentNodes": [
|
||||
"859:2211",
|
||||
"859:2403",
|
||||
"859:2418",
|
||||
"859:2612",
|
||||
"859:2627"
|
||||
],
|
||||
"sourceName": "3 4456240",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Decorative avatar ring shared by both relationship members",
|
||||
"bounds": { "width": 66, "height": 66 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 4406,
|
||||
"sha256": "a2d56dd2b81fafa471ca6a41f7f59df9d8aba2c29c9e90f5348d6818b4326e30",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "level-cp.png",
|
||||
"sourceNode": "859:2218",
|
||||
"equivalentNodes": ["859:2228"],
|
||||
"sourceName": "image 4816",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "CP level badge background without level text",
|
||||
"bounds": { "width": 24, "height": 13 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 1016,
|
||||
"sha256": "36e0a7bbcf4cb5d39bd61153b8ca1cf4e76e8c3293c064127e5c097bf7a5d148",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "level-brother.png",
|
||||
"sourceNode": "859:2425",
|
||||
"equivalentNodes": ["859:2435"],
|
||||
"sourceName": "image 4817",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Brother level badge background without level text",
|
||||
"bounds": { "width": 24, "height": 13 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 1017,
|
||||
"sha256": "da0b413aab96eb7890af30f9a3daef16c2ea03d607767430f2b8c6fb483b4b4b",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "level-sister.png",
|
||||
"sourceNode": "859:2634",
|
||||
"equivalentNodes": ["859:2644"],
|
||||
"sourceName": "image 4819",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Sister level badge background without level text",
|
||||
"bounds": { "width": 24, "height": 13 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 967,
|
||||
"sha256": "ac5339c0505f194757e1ae0f67e3e69633f73a8c19f99355b956c0d1b038024c",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "pill-cp-left.png",
|
||||
"sourceNode": "859:2191",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2085668693 2",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Left CP EXP pill cap without dynamic text",
|
||||
"bounds": { "width": 49, "height": 28 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 2196,
|
||||
"sha256": "01a8328f9e1f2ebc0973f5cdecb104af634bb28c5d47f5b75d78d0837d7bafdd",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "pill-cp-center.png",
|
||||
"sourceNode": "859:2192",
|
||||
"equivalentNodes": [
|
||||
"859:2193",
|
||||
"859:2194",
|
||||
"859:2195",
|
||||
"859:2196",
|
||||
"859:2197",
|
||||
"859:2198",
|
||||
"859:2199",
|
||||
"859:2200",
|
||||
"859:2201",
|
||||
"859:2202",
|
||||
"859:2203",
|
||||
"859:2204",
|
||||
"859:2205"
|
||||
],
|
||||
"sourceName": "Group 2085668693 4",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Repeatable CP EXP pill center slice without dynamic text",
|
||||
"bounds": { "width": 1, "height": 28 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 241,
|
||||
"sha256": "e9940fdbe009e5db2c38992609d56bc162262da870bb5032cc08218aa979444c",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "pill-cp-right.png",
|
||||
"sourceNode": "859:2206",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Group 2085668693 3",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Right CP EXP pill cap without dynamic text",
|
||||
"bounds": { "width": 49, "height": 28 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 2236,
|
||||
"sha256": "1ad302ea0c693b0544e666c0a0289a42eaf27cd1a3b210040cf1fe7fc839fa9d",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "pill-brother.png",
|
||||
"sourceNode": "859:2411",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Mask group",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Brother EXP pill background without dynamic text",
|
||||
"bounds": { "width": 112, "height": 28 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 5154,
|
||||
"sha256": "90af1c3725f500489f84e25b1a025aa20ca6bc6edab56087f108a32dfeecccfc",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "pill-sister.png",
|
||||
"sourceNode": "859:2620",
|
||||
"equivalentNodes": [],
|
||||
"sourceName": "Mask group",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Sister EXP pill background without dynamic text",
|
||||
"bounds": { "width": 112, "height": 28 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 5095,
|
||||
"sha256": "b7587a4b7b2e1c40bbb9cc42689349351f55b933ab0a687d4ea4a4e2c65440d9",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "back.svg",
|
||||
"sourceNode": "859:2051",
|
||||
"equivalentNodes": ["859:2271", "859:2478"],
|
||||
"sourceName": "icon_20_返回",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Back glyph rendered inside a real DOM button",
|
||||
"bounds": { "x": 16, "y": 54, "width": 24, "height": 24 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 419,
|
||||
"sha256": "50d06f24adb0019fcdfb3c3f5db012808c230cd8c2f06c61b775fcccdbf525d4",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "help.svg",
|
||||
"sourceNode": "859:2055",
|
||||
"equivalentNodes": ["859:2275", "859:2482"],
|
||||
"sourceName": "help",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Help glyph rendered inside a real DOM button",
|
||||
"bounds": { "x": 335, "y": 54, "width": 24, "height": 24 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1535,
|
||||
"sha256": "18087727ea208b257df9d875523dd45e29ccd0b05912a4504f72555cd15c045e",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "battery.svg",
|
||||
"sourceNode": "859:2034",
|
||||
"equivalentNodes": ["859:2254", "859:2461"],
|
||||
"sourceName": "Battery",
|
||||
"sourceType": "frame",
|
||||
"purpose": "Static Figma status-bar battery glyph",
|
||||
"bounds": {
|
||||
"x": 336.67,
|
||||
"y": 16.33,
|
||||
"width": 24.33,
|
||||
"height": 11.33
|
||||
},
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1055,
|
||||
"sha256": "e4d18984f66b6e24e3ecf95ac7a893e3ac2e863a0b52c02ff075860b30a8b837",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "wifi.svg",
|
||||
"sourceNode": "859:2038",
|
||||
"equivalentNodes": ["859:2258", "859:2465"],
|
||||
"sourceName": "Wifi",
|
||||
"sourceType": "boolean-operation",
|
||||
"purpose": "Static Figma status-bar Wi-Fi glyph",
|
||||
"bounds": {
|
||||
"x": 316.34,
|
||||
"y": 16.33,
|
||||
"width": 15.33,
|
||||
"height": 11.01
|
||||
},
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1655,
|
||||
"sha256": "d84c6cdf1a0f03a824a5b40174d8f326b62301a9c83e90a65600cfb04f1d9411",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "cellular.svg",
|
||||
"sourceNode": "859:2042",
|
||||
"equivalentNodes": ["859:2262", "859:2469"],
|
||||
"sourceName": "Cellular Connection",
|
||||
"sourceType": "boolean-operation",
|
||||
"purpose": "Static Figma status-bar cellular glyph",
|
||||
"bounds": { "x": 294.34, "y": 15, "width": 17, "height": 11.33 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1183,
|
||||
"sha256": "f5df0579b8435fef2e8a8cd8db3998c72947a409f44d25a5486f91f8adb745f3",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "mock-avatar.png",
|
||||
"sourceNode": "859:2181",
|
||||
"equivalentNodes": [
|
||||
"859:2209",
|
||||
"859:2401",
|
||||
"859:2416",
|
||||
"859:2610",
|
||||
"859:2625"
|
||||
],
|
||||
"sourceName": "image 4626",
|
||||
"sourceType": "rounded-rectangle",
|
||||
"purpose": "Figma reference avatar used only by the explicit mock visual-QA mode",
|
||||
"bounds": { "width": 61.29, "height": 61.29 },
|
||||
"exportFormat": "png",
|
||||
"bytes": 186219,
|
||||
"sha256": "31f699f244b15bdace7b0c655f254ecd79de89428bcaaae7d8d8738a65b2dcea",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": true,
|
||||
"testOnly": true
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
activity/cp-space/fami/assets/layers/mock-avatar.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
activity/cp-space/fami/assets/layers/pill-brother.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
activity/cp-space/fami/assets/layers/pill-cp-center.png
Normal file
|
After Width: | Height: | Size: 241 B |
BIN
activity/cp-space/fami/assets/layers/pill-cp-left.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
activity/cp-space/fami/assets/layers/pill-cp-right.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
activity/cp-space/fami/assets/layers/pill-sister.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
activity/cp-space/fami/assets/layers/tab-brother.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
activity/cp-space/fami/assets/layers/tab-cp.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
activity/cp-space/fami/assets/layers/tab-sister.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
3
activity/cp-space/fami/assets/layers/wifi.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 15.3333 11.0067" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Wifi" fill-rule="evenodd" clip-rule="evenodd" d="M9.99304 8.66064C9.99127 8.57386 9.95373 8.49166 9.8893 8.43345C8.60864 7.35118 6.733 7.35118 5.45234 8.43345C5.38786 8.49162 5.35027 8.57379 5.34843 8.66057C5.34659 8.74735 5.38068 8.83104 5.44263 8.89188L7.44827 10.9139C7.50705 10.9733 7.5872 11.0067 7.67082 11.0067C7.75444 11.0067 7.83458 10.9733 7.89337 10.9139L9.89867 8.89188C9.96067 8.83108 9.99482 8.74742 9.99304 8.66064V8.66064ZM13.6438 4.67028C13.7652 4.78852 13.9593 4.78703 14.0789 4.66693L15.2395 3.4966C15.3 3.43569 15.3338 3.35318 15.3333 3.26733C15.3328 3.18148 15.2981 3.09937 15.2368 3.03917C11.0049 -1.01306 4.32858 -1.01306 0.0966678 3.03917C0.0353653 3.09933 0.00057401 3.18141 6.67831e-06 3.26726C-0.000545321 3.35311 0.0331419 3.43564 0.0936471 3.4966L1.25459 4.66693C1.3741 4.78721 1.56831 4.7887 1.68966 4.67028C3.30222 3.13859 5.44213 2.28447 7.66707 2.28448C9.89181 2.28457 12.0315 3.13869 13.6438 4.67028V4.67028ZM10.9782 7.36722C11.1007 7.48356 11.2938 7.48104 11.4132 7.36153L12.5725 6.1912C12.6336 6.12982 12.6674 6.04654 12.6665 5.96001C12.6657 5.87347 12.6301 5.7909 12.5678 5.73077C9.80866 3.16636 5.53574 3.16636 2.77658 5.73077C2.71425 5.7909 2.67869 5.87351 2.67787 5.96008C2.67706 6.04664 2.71106 6.12991 2.77223 6.1912L3.93117 7.36153C4.05063 7.48104 4.24368 7.48356 4.36623 7.36722C5.2717 6.54799 6.44949 6.09407 7.67102 6.09357C8.89337 6.09349 10.0721 6.54745 10.9782 7.36722V7.36722Z" fill="var(--fill-0, white)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
681
activity/cp-space/fami/fami.css
Normal file
@ -0,0 +1,681 @@
|
||||
@font-face {
|
||||
font-family: 'Source Han Sans SC Fami';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('./assets/fonts/source-han-sans-sc-medium-latin.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
background: #440b23;
|
||||
color: #fff;
|
||||
font-family:
|
||||
'SF Pro Text', 'SF Pro Display', 'Noto Sans Arabic', Arial,
|
||||
'Helvetica Neue', Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.fami-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 100svh;
|
||||
background: #440b23;
|
||||
}
|
||||
|
||||
.fami-page[data-tab='brother'] {
|
||||
background: #080636;
|
||||
}
|
||||
|
||||
.fami-page[data-tab='sister'] {
|
||||
background: #301008;
|
||||
}
|
||||
|
||||
.fami-stage {
|
||||
--design-scale: 1;
|
||||
--design-height: 812px;
|
||||
--stage-height: 812px;
|
||||
--accent: #ff0979;
|
||||
--accent-soft: #ff5762;
|
||||
--tab-bg: rgba(94, 13, 35, 0.8);
|
||||
--tab-glow: #ff7948;
|
||||
--tab-jewel-left: -7px;
|
||||
--avatar-top: 35px;
|
||||
--center-height: 120px;
|
||||
--exp-top: 102px;
|
||||
--progress-from: #ff1e37;
|
||||
--progress-mid: #fe6a30;
|
||||
--progress-to: #ffcf67;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: min(100vw, 414px);
|
||||
height: var(--stage-height);
|
||||
overflow: hidden;
|
||||
background: #440b23;
|
||||
}
|
||||
|
||||
.fami-page[data-tab='brother'] .fami-stage {
|
||||
--accent: #5709ff;
|
||||
--accent-soft: #5709ff;
|
||||
--tab-bg: rgba(17, 13, 94, 0.8);
|
||||
--tab-glow: #4872ff;
|
||||
--tab-jewel-left: 110px;
|
||||
--avatar-top: 41px;
|
||||
--center-height: 136px;
|
||||
--exp-top: 102px;
|
||||
--progress-from: #471eff;
|
||||
--progress-mid: #30bcfe;
|
||||
--progress-to: #67acff;
|
||||
background: #080636;
|
||||
}
|
||||
|
||||
.fami-page[data-tab='sister'] .fami-stage {
|
||||
--accent: #ff4a09;
|
||||
--accent-soft: #ff4a09;
|
||||
--tab-bg: rgba(94, 29, 13, 0.8);
|
||||
--tab-glow: #ff7948;
|
||||
--tab-jewel-left: 220px;
|
||||
--avatar-top: 41px;
|
||||
--center-height: 136px;
|
||||
--exp-top: 93px;
|
||||
--progress-from: #ff5e1e;
|
||||
--progress-mid: #fe6a30;
|
||||
--progress-to: #ffb167;
|
||||
background: #301008;
|
||||
}
|
||||
|
||||
.fami-design {
|
||||
position: relative;
|
||||
width: 375px;
|
||||
height: var(--design-height);
|
||||
transform: scale(var(--design-scale));
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
.fami-background,
|
||||
.fami-card-skin,
|
||||
.fami-tab-jewel,
|
||||
.fami-center-art,
|
||||
.fami-avatar-frame,
|
||||
.fami-level-badge img,
|
||||
.fami-status-bar img,
|
||||
.fami-nav-button img {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fami-background {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
width: 375px;
|
||||
height: 812px;
|
||||
object-fit: fill;
|
||||
object-position: center top;
|
||||
}
|
||||
|
||||
.fami-status-bar {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 375px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.fami-page.has-native-status .fami-status-bar {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.fami-status-time {
|
||||
position: absolute;
|
||||
left: 33.87px;
|
||||
top: 13px;
|
||||
width: 54px;
|
||||
height: 15px;
|
||||
color: #fff;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fami-status-cellular {
|
||||
position: absolute;
|
||||
left: 294.34px;
|
||||
top: 15px;
|
||||
width: 17px;
|
||||
height: 11.33px;
|
||||
}
|
||||
|
||||
.fami-status-wifi {
|
||||
position: absolute;
|
||||
left: 316.34px;
|
||||
top: 16.33px;
|
||||
width: 15.33px;
|
||||
height: 11.01px;
|
||||
}
|
||||
|
||||
.fami-status-battery {
|
||||
position: absolute;
|
||||
left: 336.67px;
|
||||
top: 16.33px;
|
||||
width: 24.33px;
|
||||
height: 11.33px;
|
||||
}
|
||||
|
||||
.fami-nav {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
left: 0;
|
||||
top: 44px;
|
||||
width: 375px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.fami-nav h1 {
|
||||
position: absolute;
|
||||
left: 139px;
|
||||
top: 13px;
|
||||
width: 97px;
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-family: 'Source Han Sans SC Fami', 'Noto Sans Arabic', sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fami-nav-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.fami-nav-button--back {
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.fami-nav-button--help {
|
||||
left: 335px;
|
||||
}
|
||||
|
||||
.fami-nav-button img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.fami-nav-button--help img {
|
||||
width: 25.4px;
|
||||
height: 25.4px;
|
||||
transform: translate(-0.7px, -0.7px);
|
||||
}
|
||||
|
||||
.fami-tabs {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
left: 16px;
|
||||
top: 109px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
width: 343px;
|
||||
height: 37px;
|
||||
border: 0.5px solid #f4b747;
|
||||
border-radius: 32px;
|
||||
background: var(--tab-bg);
|
||||
box-shadow: inset 0 0 5.5px var(--tab-glow);
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.fami-tab-jewel {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
left: calc(var(--tab-jewel-left) - 0.5px);
|
||||
top: -2.5px;
|
||||
width: 130px;
|
||||
height: 48px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.fami-tabs button {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 37px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-family: 'SF Pro Text', Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fami-tabs button[aria-selected='true'] {
|
||||
color: #fff;
|
||||
font-family: 'Source Han Sans SC Fami', 'Noto Sans Arabic', sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fami-tabs button[data-tab='brother'][aria-selected='false'],
|
||||
.fami-tabs button[data-tab='sister'][aria-selected='false'] {
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.fami-tabs button[data-tab='brother'][aria-selected='false'] {
|
||||
letter-spacing: 0.57px;
|
||||
}
|
||||
|
||||
.fami-tabs button[data-tab='cp'][aria-selected='true'] {
|
||||
letter-spacing: 0.35px;
|
||||
}
|
||||
|
||||
.fami-tabs button[data-tab='brother'][aria-selected='true'] {
|
||||
letter-spacing: -0.384px;
|
||||
}
|
||||
|
||||
.fami-tabs button[data-tab='sister'][aria-selected='true'] {
|
||||
letter-spacing: 0.042px;
|
||||
}
|
||||
|
||||
.fami-tabs button span {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.fami-tabs button[data-tab='cp'][aria-selected='true'] span {
|
||||
left: -0.5px;
|
||||
}
|
||||
|
||||
.fami-tabs button[data-tab='brother'] span,
|
||||
.fami-tabs button[data-tab='sister'] span {
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.fami-content {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 375px;
|
||||
}
|
||||
|
||||
.fami-relation-list {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
top: 169px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
width: 347px;
|
||||
}
|
||||
|
||||
.fami-relation-card {
|
||||
position: relative;
|
||||
flex: 0 0 199px;
|
||||
width: 347px;
|
||||
height: 199px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.fami-card-skin {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
width: 347px;
|
||||
height: 199px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.fami-avatar {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: var(--avatar-top);
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fami-avatar--left {
|
||||
left: 32px;
|
||||
}
|
||||
|
||||
.fami-avatar--right {
|
||||
left: 250px;
|
||||
}
|
||||
|
||||
.fami-avatar-photo {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 2.36px;
|
||||
top: 2.36px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 61.29px;
|
||||
height: 61.29px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.26);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fami-avatar-photo img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.fami-avatar-frame {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 0;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.fami-center-art-wrap {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 106px;
|
||||
top: 6px;
|
||||
width: 136px;
|
||||
height: var(--center-height);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fami-center-art {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
width: 136px;
|
||||
height: var(--center-height);
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.fami-days-value {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
top: 45px;
|
||||
color: var(--accent);
|
||||
font-size: 18px;
|
||||
font-family: 'SF Pro Text', Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
text-shadow:
|
||||
0 1px 0 #fff,
|
||||
0 0 4px rgba(255, 255, 255, 0.75);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.fami-days-unit {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
top: 70px;
|
||||
color: var(--accent);
|
||||
font-family: 'SF Pro Text', Arial, sans-serif;
|
||||
font-size: 8px;
|
||||
font-weight: 400;
|
||||
line-height: 8px;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.fami-exp-pill {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 119px;
|
||||
top: var(--exp-top);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 112px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.fami-exp-pill-skin {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
inset: 0;
|
||||
display: none;
|
||||
width: 112px;
|
||||
height: 28px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fami-page[data-tab='cp'] .fami-exp-pill-skin--cp,
|
||||
.fami-page[data-tab='brother'] .fami-exp-pill-skin--brother,
|
||||
.fami-page[data-tab='sister'] .fami-exp-pill-skin--sister {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fami-exp-pill-skin--cp img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: block;
|
||||
height: 28px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fami-exp-pill-skin--cp img:nth-child(1) {
|
||||
left: 0;
|
||||
width: 49px;
|
||||
}
|
||||
|
||||
.fami-exp-pill-skin--cp img:nth-child(2) {
|
||||
left: 49px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.fami-exp-pill-skin--cp img:nth-child(3) {
|
||||
left: 63px;
|
||||
width: 49px;
|
||||
}
|
||||
|
||||
.fami-exp-pill [data-value='exp-away'] {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: var(--accent);
|
||||
font-family: 'SF Pro Text', Arial, sans-serif;
|
||||
font-size: 8px;
|
||||
font-weight: 400;
|
||||
line-height: 8px;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
0 1px 0 rgba(255, 255, 255, 0.95),
|
||||
0 0 2px rgba(255, 255, 255, 0.85);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fami-progress-block {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 32px;
|
||||
top: 134px;
|
||||
width: 289px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.fami-level-badge {
|
||||
position: absolute;
|
||||
top: 1.5px;
|
||||
width: 24px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.fami-level-badge--left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.fami-level-badge--right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.fami-level-badge img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 24px;
|
||||
height: 13px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.fami-level-badge span {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff;
|
||||
font-family: 'SF Pro Text', Arial, sans-serif;
|
||||
font-size: 9px;
|
||||
font-weight: 500;
|
||||
line-height: 9px;
|
||||
}
|
||||
|
||||
.fami-progress-track {
|
||||
position: absolute;
|
||||
left: 28px;
|
||||
top: 0;
|
||||
width: 233px;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 74px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.fami-progress-fill {
|
||||
position: absolute;
|
||||
left: 1px;
|
||||
top: 1px;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
overflow: hidden;
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--progress-from) 0%,
|
||||
var(--progress-mid) 50%,
|
||||
var(--progress-to) 100%
|
||||
);
|
||||
box-shadow:
|
||||
inset -4px 0 3px #fffef8,
|
||||
inset 0 2px 2px rgba(255, 254, 239, 0.48),
|
||||
inset 0 -2px 2px rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
.fami-progress-fill::after {
|
||||
position: absolute;
|
||||
inset: 1px 2px;
|
||||
border-radius: inherit;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(255, 255, 255, 0.95) 0 1px,
|
||||
transparent 2px
|
||||
)
|
||||
0 0 / 7px 7px;
|
||||
content: '';
|
||||
mix-blend-mode: screen;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.fami-progress-text {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 2px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 14px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.fami-time-text {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 20px;
|
||||
width: 289px;
|
||||
margin: 0;
|
||||
color: var(--accent-soft);
|
||||
font-family: 'SF Pro Text', Arial, sans-serif;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
0 1px 0 rgba(255, 255, 255, 0.82),
|
||||
0 0 2px rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.fami-state {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
top: 169px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 347px;
|
||||
min-height: 199px;
|
||||
padding: 24px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.45);
|
||||
border-radius: 12px;
|
||||
background: rgba(20, 8, 35, 0.76);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fami-state[data-retry='true'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fami-nav-button:focus-visible,
|
||||
.fami-tabs button:focus-visible,
|
||||
.fami-state:focus-visible {
|
||||
outline: 2px solid var(--hy-theme-focus-ring, rgba(255, 255, 255, 0.75));
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
[dir='rtl'] .fami-nav-button--back img {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
673
activity/cp-space/fami/fami.js
Normal file
@ -0,0 +1,673 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var VALID_TABS = ['cp', 'brother', 'sister'];
|
||||
var LAYER_BASE = './fami/assets/layers/';
|
||||
var THEMES = {
|
||||
cp: {
|
||||
background: 'bg-cp.png',
|
||||
tab: 'tab-cp.png',
|
||||
card: 'card-cp.png',
|
||||
center: 'center-cp.png',
|
||||
level: 'level-cp.png',
|
||||
},
|
||||
brother: {
|
||||
background: 'bg-brother.png',
|
||||
tab: 'tab-brother.png',
|
||||
card: 'card-brother.png',
|
||||
center: 'center-brother.png',
|
||||
level: 'level-brother.png',
|
||||
},
|
||||
sister: {
|
||||
background: 'bg-sister.png',
|
||||
tab: 'tab-sister.png',
|
||||
card: 'card-sister.png',
|
||||
center: 'center-sister.png',
|
||||
level: 'level-sister.png',
|
||||
},
|
||||
};
|
||||
|
||||
var page = document.getElementById('famiPage');
|
||||
var stage = document.getElementById('famiStage');
|
||||
var design = document.getElementById('famiDesign');
|
||||
var background = document.getElementById('famiBackground');
|
||||
var tabJewel = document.getElementById('famiTabJewel');
|
||||
var relationList = document.getElementById('famiRelationList');
|
||||
var stateButton = document.getElementById('famiStateButton');
|
||||
var content = document.getElementById('famiContent');
|
||||
var template = document.getElementById('famiRelationTemplate');
|
||||
var backButton = document.getElementById('famiBackButton');
|
||||
var helpButton = document.getElementById('famiHelpButton');
|
||||
var tabButtons = Array.prototype.slice.call(
|
||||
document.querySelectorAll('.fami-tabs [data-tab]')
|
||||
);
|
||||
var params = new URLSearchParams(window.location.search || '');
|
||||
var resizeObserver = null;
|
||||
|
||||
var state = {
|
||||
activeTab: normalizeTab(params.get('tab')) || 'cp',
|
||||
mock: params.get('mock') === '1',
|
||||
tabs: {
|
||||
cp: createTabState(),
|
||||
brother: createTabState(),
|
||||
sister: createTabState(),
|
||||
},
|
||||
};
|
||||
|
||||
function createTabState() {
|
||||
return {
|
||||
loading: false,
|
||||
loaded: false,
|
||||
error: '',
|
||||
relationships: [],
|
||||
requestId: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTab(value) {
|
||||
var tab = String(value || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return VALID_TABS.indexOf(tab) >= 0 ? tab : '';
|
||||
}
|
||||
|
||||
function readNumber(value, fallback) {
|
||||
var number = Number(value);
|
||||
return Number.isFinite(number) ? number : fallback || 0;
|
||||
}
|
||||
|
||||
function readString(value) {
|
||||
return String(value == null ? '' : value).trim();
|
||||
}
|
||||
|
||||
function firstValue() {
|
||||
for (var index = 0; index < arguments.length; index += 1) {
|
||||
var value = arguments[index];
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function hasValue(value) {
|
||||
return value !== undefined && value !== null && value !== '';
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
return window.HyAppI18n && window.HyAppI18n.t
|
||||
? window.HyAppI18n.t(key, fallback)
|
||||
: fallback || key;
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
var number = Math.max(0, Math.floor(readNumber(value, 0)));
|
||||
return String(number).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
}
|
||||
|
||||
function layer(fileName) {
|
||||
return LAYER_BASE + fileName;
|
||||
}
|
||||
|
||||
function setSource(node, fileName) {
|
||||
if (!node) return;
|
||||
var source = layer(fileName);
|
||||
if (node.getAttribute('src') !== source) {
|
||||
node.setAttribute('src', source);
|
||||
}
|
||||
}
|
||||
|
||||
function profileFromData(raw) {
|
||||
var profile = raw || {};
|
||||
return {
|
||||
id: readString(profile.user_id || profile.userId || profile.id),
|
||||
displayId: readString(
|
||||
profile.display_user_id || profile.displayUserId
|
||||
),
|
||||
name: readString(
|
||||
profile.username ||
|
||||
profile.nickname ||
|
||||
profile.display_name ||
|
||||
profile.name
|
||||
),
|
||||
avatar: readString(
|
||||
profile.avatar ||
|
||||
profile.avatar_url ||
|
||||
profile.avatarUrl ||
|
||||
profile.avatar_path
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function daysFromTimestamp(value) {
|
||||
var formedAt = readNumber(value, 0);
|
||||
if (formedAt <= 0) return 0;
|
||||
if (formedAt < 100000000000) formedAt *= 1000;
|
||||
if (Date.now() <= formedAt) return 1;
|
||||
return Math.floor((Date.now() - formedAt) / 86400000) + 1;
|
||||
}
|
||||
|
||||
function normalizeLevel(value) {
|
||||
var level = Math.floor(readNumber(value, 1));
|
||||
return Math.max(1, Math.min(99, level));
|
||||
}
|
||||
|
||||
function normalizeRelationship(raw, tab) {
|
||||
var item = raw || {};
|
||||
var level = normalizeLevel(item.level || item.current_level);
|
||||
var intimacy = Math.max(
|
||||
0,
|
||||
readNumber(item.intimacy_value || item.intimacyValue, 0)
|
||||
);
|
||||
var currentThreshold = Math.max(
|
||||
0,
|
||||
readNumber(
|
||||
item.current_level_threshold || item.currentLevelThreshold,
|
||||
0
|
||||
)
|
||||
);
|
||||
var nextThreshold = Math.max(
|
||||
currentThreshold,
|
||||
readNumber(item.next_level_threshold || item.nextLevelThreshold, 0)
|
||||
);
|
||||
var currentExp = hasValue(firstValue(item.current_exp, item.currentExp))
|
||||
? Math.max(
|
||||
0,
|
||||
readNumber(firstValue(item.current_exp, item.currentExp))
|
||||
)
|
||||
: Math.max(0, intimacy - currentThreshold);
|
||||
var nextExp = hasValue(firstValue(item.next_exp, item.nextExp))
|
||||
? Math.max(0, readNumber(firstValue(item.next_exp, item.nextExp)))
|
||||
: Math.max(0, nextThreshold - currentThreshold);
|
||||
if (!nextExp && intimacy > 0) nextExp = intimacy;
|
||||
var neededExp = hasValue(
|
||||
firstValue(item.needed_for_next_level, item.neededExp)
|
||||
)
|
||||
? Math.max(
|
||||
0,
|
||||
readNumber(
|
||||
firstValue(item.needed_for_next_level, item.neededExp)
|
||||
)
|
||||
)
|
||||
: Math.max(0, nextExp - currentExp);
|
||||
var explicitProgress = firstValue(
|
||||
item.level_progress_percent,
|
||||
item.progress_percent,
|
||||
item.progressPercent
|
||||
);
|
||||
var progressPercent = hasValue(explicitProgress)
|
||||
? readNumber(explicitProgress, 0)
|
||||
: nextExp > 0
|
||||
? (currentExp / nextExp) * 100
|
||||
: item.max_level
|
||||
? 100
|
||||
: 0;
|
||||
var days = Math.max(
|
||||
0,
|
||||
readNumber(
|
||||
firstValue(
|
||||
item.days,
|
||||
item.relationship_days,
|
||||
daysFromTimestamp(item.formed_at_ms || item.formedAtMS)
|
||||
),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
id: readString(
|
||||
item.relationship_id || item.relationshipId || item.id
|
||||
),
|
||||
relationType:
|
||||
normalizeTab(item.relation_type || item.relationType) || tab,
|
||||
status: readString(item.status || 'active').toLowerCase(),
|
||||
level: level,
|
||||
nextLevel: normalizeLevel(
|
||||
firstValue(item.next_level, item.nextLevel, level + 1)
|
||||
),
|
||||
days: days,
|
||||
togetherDays: Math.max(
|
||||
0,
|
||||
readNumber(
|
||||
firstValue(
|
||||
item.together_days,
|
||||
item.togetherDays,
|
||||
item.time_spent_days,
|
||||
days
|
||||
),
|
||||
days
|
||||
)
|
||||
),
|
||||
currentExp: currentExp,
|
||||
nextExp: nextExp,
|
||||
neededExp: neededExp,
|
||||
progressPercent: Math.max(0, Math.min(100, progressPercent)),
|
||||
maxLevel: Boolean(item.max_level || item.maxLevel),
|
||||
me: profileFromData(item.me || item.me_profile),
|
||||
partner: profileFromData(
|
||||
item.partner || item.partner_profile || item.cp_user
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function activeRelationships(payload, tab) {
|
||||
var data = payload || {};
|
||||
var rows = Array.isArray(data)
|
||||
? data
|
||||
: data.relationships || data.items || [];
|
||||
return (Array.isArray(rows) ? rows : [])
|
||||
.map(function (item) {
|
||||
return normalizeRelationship(item, tab);
|
||||
})
|
||||
.filter(function (item) {
|
||||
return (
|
||||
item.relationType === tab &&
|
||||
(!item.status || item.status === 'active')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function mockRelationship(tab) {
|
||||
return normalizeRelationship(
|
||||
{
|
||||
relationship_id: 'figma-' + tab,
|
||||
relation_type: tab,
|
||||
status: 'active',
|
||||
level: 1,
|
||||
next_level: 2,
|
||||
days: 999,
|
||||
together_days: 4,
|
||||
current_exp: 1000,
|
||||
next_exp: 200000,
|
||||
needed_for_next_level: 888,
|
||||
level_progress_percent: 22.3175965665,
|
||||
me: {
|
||||
user_id: 'figma-left',
|
||||
username: 'Fami',
|
||||
avatar: layer('mock-avatar.png'),
|
||||
},
|
||||
partner: {
|
||||
user_id: 'figma-right',
|
||||
username: 'Fami',
|
||||
avatar: layer('mock-avatar.png'),
|
||||
},
|
||||
},
|
||||
tab
|
||||
);
|
||||
}
|
||||
|
||||
function renderAvatar(node, profile) {
|
||||
if (!node) return;
|
||||
node.replaceChildren();
|
||||
var data = profile || {};
|
||||
if (data.avatar) {
|
||||
var image = document.createElement('img');
|
||||
image.src = data.avatar;
|
||||
image.alt = data.name || '';
|
||||
image.addEventListener('error', function () {
|
||||
image.remove();
|
||||
node.textContent = (data.name || data.displayId || '?')
|
||||
.charAt(0)
|
||||
.toUpperCase();
|
||||
});
|
||||
node.appendChild(image);
|
||||
return;
|
||||
}
|
||||
node.textContent = (data.name || data.displayId || '?')
|
||||
.charAt(0)
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
function renderRelationship(item, tab) {
|
||||
var theme = THEMES[tab];
|
||||
var fragment = template.content.cloneNode(true);
|
||||
var card = fragment.querySelector('.fami-relation-card');
|
||||
var levelText = t('cpSpace.levelValue', 'Lv.{level}').replace(
|
||||
'{level}',
|
||||
formatNumber(item.level)
|
||||
);
|
||||
var nextLevel = item.maxLevel ? item.level : item.nextLevel;
|
||||
var expAwayText = item.maxLevel
|
||||
? t('cpSpace.maxLevelExp', '{exp} EXP').replace(
|
||||
'{exp}',
|
||||
formatNumber(item.currentExp)
|
||||
)
|
||||
: t('cpSpace.expAway', '{exp} Exp away from Lv. {level}')
|
||||
.replace('{exp}', formatNumber(item.neededExp))
|
||||
.replace('{level}', formatNumber(nextLevel));
|
||||
|
||||
setSource(card.querySelector('[data-layer="card"]'), theme.card);
|
||||
setSource(card.querySelector('[data-layer="center"]'), theme.center);
|
||||
card.querySelectorAll('[data-layer="level"]').forEach(function (node) {
|
||||
setSource(node, theme.level);
|
||||
});
|
||||
renderAvatar(card.querySelector('[data-avatar="me"]'), item.me);
|
||||
renderAvatar(
|
||||
card.querySelector('[data-avatar="partner"]'),
|
||||
item.partner
|
||||
);
|
||||
card.querySelector('[data-value="days"]').textContent = formatNumber(
|
||||
item.days
|
||||
);
|
||||
card.querySelector('[data-value="exp-away"]').textContent = expAwayText;
|
||||
// 两侧徽标在 Figma 中表示这段关系当前等级,不是“当前/下一等级”;下一等级只出现在经验提示里。
|
||||
card.querySelector('[data-value="level-left"]').textContent = levelText;
|
||||
card.querySelector('[data-value="level-right"]').textContent =
|
||||
levelText;
|
||||
card.querySelector('[data-value="progress-text"]').textContent =
|
||||
String(Math.max(0, Math.floor(item.currentExp))) +
|
||||
'/' +
|
||||
String(Math.max(0, Math.floor(item.nextExp)));
|
||||
card.querySelector('[data-value="progress"]').style.width =
|
||||
Math.max(1, Math.min(233, (item.progressPercent / 100) * 233)) +
|
||||
'px';
|
||||
card.querySelector('[data-value="time"]').textContent = t(
|
||||
'cpSpace.timeTogether',
|
||||
'Time spent together: {days} days'
|
||||
).replace('{days}', formatNumber(item.togetherDays));
|
||||
card.setAttribute(
|
||||
'aria-label',
|
||||
t('cpSpace.levelCard', 'Best friend level {level}').replace(
|
||||
'{level}',
|
||||
formatNumber(item.level)
|
||||
)
|
||||
);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
function renderTheme() {
|
||||
var tab = state.activeTab;
|
||||
var theme = THEMES[tab];
|
||||
page.setAttribute('data-tab', tab);
|
||||
setSource(background, theme.background);
|
||||
setSource(tabJewel, theme.tab);
|
||||
tabButtons.forEach(function (button) {
|
||||
var selected = button.getAttribute('data-tab') === tab;
|
||||
button.setAttribute('aria-selected', String(selected));
|
||||
button.tabIndex = selected ? 0 : -1;
|
||||
if (selected) content.setAttribute('aria-labelledby', button.id);
|
||||
});
|
||||
}
|
||||
|
||||
function setStateMessage(message, retry) {
|
||||
stateButton.hidden = !message;
|
||||
stateButton.textContent = message || '';
|
||||
stateButton.setAttribute('data-retry', String(Boolean(retry)));
|
||||
stateButton.disabled = !retry;
|
||||
}
|
||||
|
||||
function updateDesignHeight(cardCount) {
|
||||
var count = Math.max(1, Number(cardCount || 0));
|
||||
var designHeight = Math.max(
|
||||
812,
|
||||
169 + count * 199 + (count - 1) * 14 + 40
|
||||
);
|
||||
design.style.setProperty('--design-height', designHeight + 'px');
|
||||
stage.style.setProperty('--design-height', designHeight + 'px');
|
||||
syncScale();
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderTheme();
|
||||
var tabState = state.tabs[state.activeTab];
|
||||
content.setAttribute('aria-busy', String(tabState.loading));
|
||||
relationList.replaceChildren();
|
||||
|
||||
if (tabState.loading && !tabState.loaded) {
|
||||
setStateMessage(t('cpSpace.loading', 'Loading...'), false);
|
||||
updateDesignHeight(0);
|
||||
return;
|
||||
}
|
||||
if (tabState.error) {
|
||||
setStateMessage(
|
||||
tabState.error ||
|
||||
t('cpSpace.loadFailed', 'Load failed. Tap to retry.'),
|
||||
true
|
||||
);
|
||||
updateDesignHeight(0);
|
||||
return;
|
||||
}
|
||||
if (!tabState.relationships.length) {
|
||||
setStateMessage(
|
||||
t('cpSpace.empty', 'No relationship data yet.'),
|
||||
false
|
||||
);
|
||||
updateDesignHeight(0);
|
||||
return;
|
||||
}
|
||||
|
||||
setStateMessage('', false);
|
||||
tabState.relationships.forEach(function (item) {
|
||||
relationList.appendChild(renderRelationship(item, state.activeTab));
|
||||
});
|
||||
updateDesignHeight(tabState.relationships.length);
|
||||
}
|
||||
|
||||
function syncScale() {
|
||||
if (!stage) return;
|
||||
var width = stage.getBoundingClientRect().width || window.innerWidth;
|
||||
var scale = Math.max(0.1, width / 375);
|
||||
var designHeight = parseFloat(
|
||||
stage.style.getPropertyValue('--design-height') || '812'
|
||||
);
|
||||
stage.style.setProperty('--design-scale', String(scale));
|
||||
stage.style.setProperty('--stage-height', designHeight * scale + 'px');
|
||||
}
|
||||
|
||||
function bindScale() {
|
||||
syncScale();
|
||||
if (window.ResizeObserver) {
|
||||
resizeObserver = new ResizeObserver(syncScale);
|
||||
resizeObserver.observe(stage);
|
||||
return;
|
||||
}
|
||||
window.addEventListener('resize', syncScale);
|
||||
window.addEventListener('orientationchange', syncScale);
|
||||
}
|
||||
|
||||
function syncURL(tab) {
|
||||
if (!window.history || !window.history.replaceState) return;
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('tab', tab);
|
||||
// tab 只属于本页视觉和查询状态;replaceState 避免每次切换都污染 App WebView 的返回栈。
|
||||
window.history.replaceState(null, '', url.toString());
|
||||
}
|
||||
|
||||
function loadTab(tab, force) {
|
||||
var target = normalizeTab(tab) || 'cp';
|
||||
var tabState = state.tabs[target];
|
||||
if (!force && (tabState.loading || tabState.loaded)) {
|
||||
render();
|
||||
return Promise.resolve(tabState.relationships);
|
||||
}
|
||||
var requestId = ++tabState.requestId;
|
||||
tabState.loading = true;
|
||||
tabState.error = '';
|
||||
render();
|
||||
|
||||
if (state.mock) {
|
||||
tabState.relationships = [mockRelationship(target)];
|
||||
tabState.loaded = true;
|
||||
tabState.loading = false;
|
||||
render();
|
||||
return Promise.resolve(tabState.relationships);
|
||||
}
|
||||
|
||||
if (
|
||||
!window.HyAppAPI ||
|
||||
!window.HyAppAPI.cpSpace ||
|
||||
!window.HyAppAPI.cpSpace.relationships
|
||||
) {
|
||||
tabState.loading = false;
|
||||
tabState.error = t(
|
||||
'cpSpace.loadFailed',
|
||||
'Load failed. Tap to retry.'
|
||||
);
|
||||
render();
|
||||
return Promise.reject(new Error('cp_space_api_unavailable'));
|
||||
}
|
||||
|
||||
// 关系类型和分页值原样交给 gateway;页面不猜测用户关系,也不把一个 tab 的结果复制到另一个 tab。
|
||||
return window.HyAppAPI.cpSpace
|
||||
.relationships({
|
||||
relation_type: target,
|
||||
page: 1,
|
||||
page_size: target === 'cp' ? 1 : 20,
|
||||
})
|
||||
.then(function (payload) {
|
||||
if (requestId !== tabState.requestId) return [];
|
||||
tabState.relationships = activeRelationships(payload, target);
|
||||
tabState.loaded = true;
|
||||
return tabState.relationships;
|
||||
})
|
||||
.catch(function (error) {
|
||||
if (requestId !== tabState.requestId) return [];
|
||||
tabState.error =
|
||||
(error && error.message) ||
|
||||
t('cpSpace.loadFailed', 'Load failed. Tap to retry.');
|
||||
return [];
|
||||
})
|
||||
.then(function (rows) {
|
||||
if (requestId !== tabState.requestId) return rows;
|
||||
tabState.loading = false;
|
||||
render();
|
||||
return rows;
|
||||
});
|
||||
}
|
||||
|
||||
function setActiveTab(tab, options) {
|
||||
var target = normalizeTab(tab) || 'cp';
|
||||
state.activeTab = target;
|
||||
render();
|
||||
if (!options || options.syncURL !== false) syncURL(target);
|
||||
return loadTab(target, Boolean(options && options.force));
|
||||
}
|
||||
|
||||
function hasNativeBridge() {
|
||||
return Boolean(
|
||||
(window.flutter_inappwebview &&
|
||||
window.flutter_inappwebview.callHandler) ||
|
||||
(window.HyAppBridgeChannel &&
|
||||
window.HyAppBridgeChannel.postMessage) ||
|
||||
(window.FlutterBridge && window.FlutterBridge.postMessage) ||
|
||||
(window.webkit &&
|
||||
window.webkit.messageHandlers &&
|
||||
window.webkit.messageHandlers.HyAppBridge)
|
||||
);
|
||||
}
|
||||
|
||||
function contextualURL(path) {
|
||||
var url = new URL(path, window.location.href);
|
||||
['token', 'access_token', 'env', 'language', 'lang', 'locale'].forEach(
|
||||
function (key) {
|
||||
if (params.has(key)) url.searchParams.set(key, params.get(key));
|
||||
}
|
||||
);
|
||||
url.searchParams.set('app_code', 'fami');
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function applyExternalData(payload) {
|
||||
var data = payload || {};
|
||||
VALID_TABS.forEach(function (tab) {
|
||||
if (!data[tab]) return;
|
||||
state.tabs[tab].relationships = activeRelationships(
|
||||
Array.isArray(data[tab])
|
||||
? { relationships: data[tab] }
|
||||
: data[tab],
|
||||
tab
|
||||
);
|
||||
state.tabs[tab].loaded = true;
|
||||
state.tabs[tab].loading = false;
|
||||
state.tabs[tab].error = '';
|
||||
});
|
||||
if (normalizeTab(data.activeTab)) state.activeTab = data.activeTab;
|
||||
render();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
tabButtons.forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
setActiveTab(button.getAttribute('data-tab'));
|
||||
});
|
||||
button.addEventListener('keydown', function (event) {
|
||||
var current = VALID_TABS.indexOf(
|
||||
button.getAttribute('data-tab')
|
||||
);
|
||||
var next = current;
|
||||
if (event.key === 'ArrowLeft') next = current - 1;
|
||||
else if (event.key === 'ArrowRight') next = current + 1;
|
||||
else if (event.key === 'Home') next = 0;
|
||||
else if (event.key === 'End') next = VALID_TABS.length - 1;
|
||||
else return;
|
||||
event.preventDefault();
|
||||
// 标签视觉顺序固定为从左到右;键盘同样按可见顺序循环,RTL 只翻转返回箭头而不倒置关系类型。
|
||||
next = (next + VALID_TABS.length) % VALID_TABS.length;
|
||||
var targetButton = tabButtons[next];
|
||||
targetButton.focus();
|
||||
setActiveTab(targetButton.getAttribute('data-tab'));
|
||||
});
|
||||
});
|
||||
|
||||
stateButton.addEventListener('click', function () {
|
||||
if (stateButton.getAttribute('data-retry') === 'true') {
|
||||
loadTab(state.activeTab, true);
|
||||
}
|
||||
});
|
||||
|
||||
backButton.addEventListener('click', function () {
|
||||
if (
|
||||
window.FlutterPageControl &&
|
||||
window.FlutterPageControl.postMessage
|
||||
) {
|
||||
window.FlutterPageControl.postMessage('close_page');
|
||||
return;
|
||||
}
|
||||
if (hasNativeBridge() && window.HyAppBridge) {
|
||||
window.HyAppBridge.back();
|
||||
return;
|
||||
}
|
||||
window.history.back();
|
||||
});
|
||||
|
||||
helpButton.addEventListener('click', function () {
|
||||
if (hasNativeBridge() && window.HyAppBridge) {
|
||||
window.HyAppBridge.post('cpSpaceHelpTap', {
|
||||
page: 'fami-cp-space',
|
||||
relation_type: state.activeTab,
|
||||
});
|
||||
return;
|
||||
}
|
||||
window.location.href = contextualURL('../../cp-rules/');
|
||||
});
|
||||
|
||||
window.addEventListener('hyapp:i18n-ready', render);
|
||||
window.addEventListener('hyapp:cp-space-fami-data', function (event) {
|
||||
applyExternalData(event.detail || {});
|
||||
});
|
||||
window.addEventListener('hyapp:cp-space-data', function (event) {
|
||||
applyExternalData(event.detail || {});
|
||||
});
|
||||
}
|
||||
|
||||
if (hasNativeBridge() && params.get('figma_status') !== '1') {
|
||||
page.classList.add('has-native-status');
|
||||
}
|
||||
bindScale();
|
||||
bindEvents();
|
||||
if (window.HyAppCPSpaceFamiData) {
|
||||
applyExternalData(window.HyAppCPSpaceFamiData);
|
||||
setActiveTab(state.activeTab, { syncURL: false });
|
||||
} else {
|
||||
setActiveTab(state.activeTab, { syncURL: false });
|
||||
}
|
||||
|
||||
if (window.HyAppBridge && window.HyAppBridge.ready) {
|
||||
window.HyAppBridge.ready({
|
||||
page: 'fami-cp-space',
|
||||
app_code: 'fami',
|
||||
tabs: VALID_TABS.slice(),
|
||||
mock: state.mock,
|
||||
});
|
||||
}
|
||||
})();
|
||||
8
activity/cp-space/fami/product.js
Normal file
@ -0,0 +1,8 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// common/api.js 在未显式传 app_code 时读取页面默认值;必须在 API 脚本执行前声明,避免 Fami 页面误落到 Lalu 租户。
|
||||
window.HyAppDefaultAppCode = 'fami';
|
||||
window.HyAppI18nSupported = ['en', 'ar', 'tr', 'es'];
|
||||
document.documentElement.setAttribute('data-hy-app-code', 'fami');
|
||||
})();
|
||||
205
activity/cp/huwaa.html
Normal file
@ -0,0 +1,205 @@
|
||||
<!doctype html>
|
||||
<html lang="en" dir="ltr" data-hy-app-code="huwaa">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
||||
/>
|
||||
<title data-i18n="cp.pageTitle">Last Weekly CP</title>
|
||||
<link rel="icon" href="data:," />
|
||||
<link rel="stylesheet" href="../../common/theme.css?v=20260717" />
|
||||
<link rel="stylesheet" href="./huwaa/huwaa.css?v=20260717" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="huwaa-cp-viewport" id="huwaaCpViewport">
|
||||
<main class="huwaa-cp-page" id="huwaaCpPage" data-tab="rank">
|
||||
<div class="huwaa-cp-stage-wrap" id="huwaaCpStageWrap">
|
||||
<section
|
||||
class="huwaa-cp-stage"
|
||||
id="huwaaCpStage"
|
||||
aria-label="Last Weekly CP activity"
|
||||
data-i18n-aria="cp.pageLabel"
|
||||
>
|
||||
<img
|
||||
class="huwaa-cp-background"
|
||||
id="huwaaCpBackground"
|
||||
src="./huwaa/assets/layers/bg-texture.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
<header class="huwaa-cp-hero">
|
||||
<img
|
||||
src="./huwaa/assets/layers/hero-art-text-free.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
draggable="false"
|
||||
/>
|
||||
<h1
|
||||
class="huwaa-cp-wordmark"
|
||||
data-i18n="cp.pageTitle"
|
||||
>
|
||||
Last Weekly CP
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<section
|
||||
class="huwaa-cp-countdown"
|
||||
aria-label="Activity countdown"
|
||||
data-i18n-aria="cp.countdownLabel"
|
||||
>
|
||||
<div class="huwaa-cp-time-unit">
|
||||
<img
|
||||
src="./huwaa/assets/layers/countdown-heart.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<strong data-count="days">00</strong>
|
||||
<span data-i18n="cp.days">D</span>
|
||||
</div>
|
||||
<div class="huwaa-cp-time-unit">
|
||||
<img
|
||||
src="./huwaa/assets/layers/countdown-heart.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<strong data-count="hours">00</strong>
|
||||
<span data-i18n="cp.hours">H</span>
|
||||
</div>
|
||||
<div class="huwaa-cp-time-unit">
|
||||
<img
|
||||
src="./huwaa/assets/layers/countdown-heart.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<strong data-count="minutes">00</strong>
|
||||
<span data-i18n="cp.minutes">M</span>
|
||||
</div>
|
||||
<div class="huwaa-cp-time-unit">
|
||||
<img
|
||||
src="./huwaa/assets/layers/countdown-heart.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<strong data-count="seconds">00</strong>
|
||||
<span data-i18n="cp.seconds">S</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<nav
|
||||
class="huwaa-cp-tabs"
|
||||
role="tablist"
|
||||
aria-label="CP activity tabs"
|
||||
data-i18n-aria="cp.tabsLabel"
|
||||
>
|
||||
<button
|
||||
class="huwaa-cp-tab"
|
||||
id="huwaaCpTabPlaza"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
aria-controls="huwaaCpTabContent"
|
||||
tabindex="-1"
|
||||
data-tab="plaza"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/tab-inactive.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span data-i18n="cp.tabPlaza">CP Plaza</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-cp-tab is-active"
|
||||
id="huwaaCpTabRank"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected="true"
|
||||
aria-controls="huwaaCpTabContent"
|
||||
data-tab="rank"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/tab-active.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span data-i18n="cp.tabRank">CP Rank</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-cp-tab"
|
||||
id="huwaaCpTabRewards"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
aria-controls="huwaaCpTabContent"
|
||||
tabindex="-1"
|
||||
data-tab="rewards"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/tab-inactive.webp"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span data-i18n="cp.tabRewards">Rewards</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<section
|
||||
class="huwaa-cp-tab-content"
|
||||
id="huwaaCpTabContent"
|
||||
role="tabpanel"
|
||||
aria-labelledby="huwaaCpTabRank"
|
||||
></section>
|
||||
|
||||
<footer
|
||||
class="huwaa-cp-copyright"
|
||||
data-i18n="cp.copyright"
|
||||
>
|
||||
The copyright of this activity belongs to Shila
|
||||
</footer>
|
||||
<p
|
||||
class="huwaa-cp-sr-only"
|
||||
id="huwaaCpAnnouncement"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
></p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.HyAppI18nSupported = ['en', 'ar', 'tr', 'es'];
|
||||
window.HyAppDefaultAppCode = 'huwaa';
|
||||
(function enforceHuwaaAppCode() {
|
||||
try {
|
||||
var entryURL = new URL(window.location.href);
|
||||
var aliases = ['app', 'app_code', 'appCode'];
|
||||
var needsReplace = aliases.some(function (key) {
|
||||
return entryURL.searchParams.get(key) !== 'huwaa';
|
||||
});
|
||||
if (needsReplace) {
|
||||
// common/api.js 与 common/params.js 为兼容旧入口会按不同顺序读取三个别名;
|
||||
// 页面在公共脚本执行前统一它们,避免 UI 主题身份与 X-App-Code 请求租户分裂。
|
||||
aliases.forEach(function (key) {
|
||||
entryURL.searchParams.set(key, 'huwaa');
|
||||
});
|
||||
window.history.replaceState(
|
||||
null,
|
||||
'',
|
||||
entryURL.toString()
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
// 部分旧 WebView 禁止修改 file:// history;默认 app code 仍会让请求层发送 Huwaa 身份。
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script src="../../common/i18n.js?v=20260717"></script>
|
||||
<script src="../../common/api.js?v=20260717"></script>
|
||||
<script src="../../common/params.js?v=20260717"></script>
|
||||
<script src="./huwaa/huwaa.js?v=20260717"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
activity/cp/huwaa/assets/layers/bg-texture.webp
Normal file
|
After Width: | Height: | Size: 695 KiB |
BIN
activity/cp/huwaa/assets/layers/countdown-heart.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
activity/cp/huwaa/assets/layers/crown-bronze.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
activity/cp/huwaa/assets/layers/crown-gold.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
activity/cp/huwaa/assets/layers/crown-silver.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
activity/cp/huwaa/assets/layers/deco-line-left.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
activity/cp/huwaa/assets/layers/deco-line-right.webp
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
activity/cp/huwaa/assets/layers/gift-plane.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
activity/cp/huwaa/assets/layers/hero-art-text-free.webp
Normal file
|
After Width: | Height: | Size: 348 KiB |
424
activity/cp/huwaa/assets/layers/manifest.json
Normal file
@ -0,0 +1,424 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"figma": {
|
||||
"fileKey": "OTOg5cwbhVNWxkImGmzTJl",
|
||||
"canvas": {
|
||||
"id": "543:1998",
|
||||
"name": "Huwaa运营"
|
||||
},
|
||||
"entryFrames": {
|
||||
"plaza": "831:3320",
|
||||
"rank": "831:4048",
|
||||
"rewards": "831:3913"
|
||||
},
|
||||
"designSizes": {
|
||||
"plaza": {
|
||||
"width": 1080,
|
||||
"height": 4283
|
||||
},
|
||||
"rank": {
|
||||
"width": 1080,
|
||||
"height": 3733
|
||||
},
|
||||
"rewards": {
|
||||
"width": 1080,
|
||||
"height": 4782
|
||||
}
|
||||
},
|
||||
"forbiddenAggregateNodes": [
|
||||
"543:1998",
|
||||
"823:3005",
|
||||
"831:3320",
|
||||
"831:4048",
|
||||
"831:3913",
|
||||
"831:3323",
|
||||
"831:3326",
|
||||
"831:3339",
|
||||
"831:3349",
|
||||
"831:3642",
|
||||
"831:4381",
|
||||
"831:3942"
|
||||
]
|
||||
},
|
||||
"assets": [
|
||||
{
|
||||
"file": "bg-texture.webp",
|
||||
"sourceNode": "831:3321",
|
||||
"sourceName": "花瓣素材_情人节白色情人节七夕520梦幻浪漫感粉丝爱心i底纹背景元素素材_193406578 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Pure petal texture positioned at x=-487, y=489 over the independent #e98c9b stage fill with Figma opacity 0.5 and screen blend mode",
|
||||
"bounds": {
|
||||
"width": 2415,
|
||||
"height": 4293
|
||||
},
|
||||
"original": {
|
||||
"width": 1242,
|
||||
"height": 2208
|
||||
},
|
||||
"exportFormat": "webp",
|
||||
"bytes": 711912,
|
||||
"sha256": "e0c9a81e2bb893731f8c70cf25f322693da52fe12e8a48513d4d7de8bd6d182c",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false,
|
||||
"derivedFromSha256": "9b13664bd6b77c64075da4891a95acef6116cc2055f3c9d9fda794493a8b6ede",
|
||||
"transform": "Figma raw image fill encoded as WebP quality 94; runtime CSS preserves the source node bounds, opacity and screen blend over the frame fill"
|
||||
},
|
||||
{
|
||||
"file": "hero-art-text-free.webp",
|
||||
"sourceNode": "831:3325",
|
||||
"sourceName": "c7ffd4a5-20dc-41c9-93f6-7721e460aa70 2",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Text-free event illustration behind the independent localized DOM wordmark; countdown, tabs, state and business data remain separate DOM layers",
|
||||
"bounds": {
|
||||
"width": 1080,
|
||||
"height": 1333
|
||||
},
|
||||
"original": {
|
||||
"width": 1080,
|
||||
"height": 1333
|
||||
},
|
||||
"exportFormat": "webp",
|
||||
"bytes": 356714,
|
||||
"sha256": "33620a230108ca0559062de12263356349c26b0b18ffbb863e5966ea23d6e194",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false,
|
||||
"derivedFromSha256": "ad52de31140cb9d72415f86b570c44aff121b0000e1aef88470b28472c1c4137",
|
||||
"transform": "Localized inpainting was limited to the embedded wordmark region; before WebP encoding, normalized source pixels above y=830 and below y=1275 were preserved, while the title was moved to independent localized DOM text",
|
||||
"preEncodingPixelExactCoverage": 0.6981
|
||||
},
|
||||
{
|
||||
"file": "countdown-heart.webp",
|
||||
"sourceNode": "831:3327",
|
||||
"sourceName": "ffutuft 3",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Empty countdown heart skin behind live DOM values",
|
||||
"bounds": {
|
||||
"width": 163,
|
||||
"height": 106
|
||||
},
|
||||
"original": {
|
||||
"width": 163,
|
||||
"height": 106
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 20946,
|
||||
"sha256": "d0a83f2b9695c2d15330d25203947dad171c9f03afbfb9d053e444765f0220fb",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "tab-active.webp",
|
||||
"sourceNode": "831:3347",
|
||||
"sourceName": "图层 0 34",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Active tab skin without its DOM label or interaction target",
|
||||
"bounds": {
|
||||
"width": 303,
|
||||
"height": 126
|
||||
},
|
||||
"original": {
|
||||
"width": 303,
|
||||
"height": 126
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 40482,
|
||||
"sha256": "dcf5231b062cfc09529493285ee42e35b7f3b0e1c1295b5fd8432573cdf9a811",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "tab-inactive.webp",
|
||||
"sourceNode": "831:3344",
|
||||
"sourceName": "图层 0 34",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Inactive tab skin without its DOM label or interaction target",
|
||||
"bounds": {
|
||||
"width": 303,
|
||||
"height": 126
|
||||
},
|
||||
"original": {
|
||||
"width": 303,
|
||||
"height": 126
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 37228,
|
||||
"sha256": "6aa5d9166eac1894fd64f014004a58afbb0a0930aa3967562cb82fb829315e91",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "panel-skin.webp",
|
||||
"sourceNode": "831:3350",
|
||||
"sourceName": "想吃vxcvxvdsfsdf 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Reusable empty pink-and-gold panel skin rendered with CSS nine-slicing",
|
||||
"bounds": {
|
||||
"width": 960,
|
||||
"height": 671
|
||||
},
|
||||
"original": {
|
||||
"width": 1254,
|
||||
"height": 1254
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 865498,
|
||||
"sha256": "b3011ae288d78d5f92a5e8d610eb889088798ab9061b739b403ad95e2ce1e5b4",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "gift-plane.webp",
|
||||
"sourceNode": "831:3568",
|
||||
"sourceName": "06黄金小飞机缩略图 (1) 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Decorative gift icon between CP partners in the plaza list",
|
||||
"bounds": {
|
||||
"width": 152,
|
||||
"height": 152
|
||||
},
|
||||
"original": {
|
||||
"width": 152,
|
||||
"height": 152
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 40268,
|
||||
"sha256": "880f57ca597bd1625700aaf3869fba1daeaefdf921c0807ff6a242695bc2e714",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "deco-line-right.webp",
|
||||
"sourceNode": "831:3583",
|
||||
"sourceName": "520 (431) 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Right-side heartbeat line between the CP avatar and center icon",
|
||||
"bounds": {
|
||||
"width": 159,
|
||||
"height": 30
|
||||
},
|
||||
"original": {
|
||||
"width": 159,
|
||||
"height": 30
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 1600,
|
||||
"sha256": "f2ecdcfa3a07d3fe9ac5b19de4d9de9c3aa802093a62f8e8227ce6a79333b23d",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "deco-line-left.webp",
|
||||
"sourceNode": "831:3584",
|
||||
"sourceName": "520 (431) 2",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Left-side heartbeat line between the CP avatar and center icon",
|
||||
"bounds": {
|
||||
"width": 159,
|
||||
"height": 30
|
||||
},
|
||||
"original": {
|
||||
"width": 159,
|
||||
"height": 30
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 1612,
|
||||
"sha256": "5e8afa2c2ab1b6117961bc9ae1162ff50213eb4be2b034293eb7421c69b246b6",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "score-heart.webp",
|
||||
"sourceNode": "831:3849",
|
||||
"sourceName": "ffutuft 2",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Empty score heart behind live DOM rank values",
|
||||
"bounds": {
|
||||
"width": 220,
|
||||
"height": 143
|
||||
},
|
||||
"original": {
|
||||
"width": 220,
|
||||
"height": 143
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 35024,
|
||||
"sha256": "4e9052c29e375553b9fecf77dbba8bfaab51b94fba30c00b500b31c776a1496b",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "podium-gold.webp",
|
||||
"sourceNode": "831:4421",
|
||||
"sourceName": "图层 0 31",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "First-place double-avatar podium frame without avatars or names",
|
||||
"bounds": {
|
||||
"width": 716,
|
||||
"height": 434
|
||||
},
|
||||
"original": {
|
||||
"width": 716,
|
||||
"height": 434
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 278480,
|
||||
"sha256": "01cab5779b58c65256b25d2dd7784a73d777c5b9a2f75d4dc22df0ea062e3a5d",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "podium-silver.webp",
|
||||
"sourceNode": "831:4426",
|
||||
"sourceName": "04144 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Second-place double-avatar podium frame without avatars or names",
|
||||
"bounds": {
|
||||
"width": 500,
|
||||
"height": 284
|
||||
},
|
||||
"original": {
|
||||
"width": 500,
|
||||
"height": 284
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 125178,
|
||||
"sha256": "13dd006d1840d1fd0ed5545446bd2a493293a3a64e8b333b9b88e3e95cbb3e57",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "podium-bronze.webp",
|
||||
"sourceNode": "831:4437",
|
||||
"sourceName": "轻微我去恶 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Third-place double-avatar podium frame without avatars or names",
|
||||
"bounds": {
|
||||
"width": 500,
|
||||
"height": 286
|
||||
},
|
||||
"original": {
|
||||
"width": 500,
|
||||
"height": 286
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 137328,
|
||||
"sha256": "060b52606840c41717664c9dc5b5721d3f69c10305d79b2f935d1f20353e65a8",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "reward-frame.webp",
|
||||
"sourceNode": "831:3950",
|
||||
"sourceName": "8d9ca1c38fb3b780ff6b5d6d3cd318b8f108e7b83f3cd-59aVD7 4",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Empty reward wreath placed around live reward cover images",
|
||||
"bounds": {
|
||||
"width": 200,
|
||||
"height": 200
|
||||
},
|
||||
"original": {
|
||||
"width": 200,
|
||||
"height": 200
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 39558,
|
||||
"sha256": "e26e9f171b44296471cd9fd0c84fed4f0835c30f54e7d48435a18162aa62bfe7",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "crown-gold.webp",
|
||||
"sourceNode": "831:3946",
|
||||
"sourceName": "冠军 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Gold reward-tier crown",
|
||||
"bounds": {
|
||||
"width": 162,
|
||||
"height": 144
|
||||
},
|
||||
"original": {
|
||||
"width": 162,
|
||||
"height": 144
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 24612,
|
||||
"sha256": "b9c26c70a5a2f22273639bcd68c4af63cd87bc104cc93676b5f6feaaf7eacaaa",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "crown-silver.webp",
|
||||
"sourceNode": "831:3981",
|
||||
"sourceName": "冠军 2",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Silver reward-tier crown",
|
||||
"bounds": {
|
||||
"width": 162,
|
||||
"height": 144
|
||||
},
|
||||
"original": {
|
||||
"width": 162,
|
||||
"height": 144
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 23164,
|
||||
"sha256": "21a95c439ca10aa615ab1b03db6f3c23f68fd8b22ddbd1690d723baf27be53f3",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "crown-bronze.webp",
|
||||
"sourceNode": "831:4016",
|
||||
"sourceName": "冠军 2",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Bronze reward-tier crown",
|
||||
"bounds": {
|
||||
"width": 162,
|
||||
"height": 144
|
||||
},
|
||||
"original": {
|
||||
"width": 162,
|
||||
"height": 144
|
||||
},
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 25124,
|
||||
"sha256": "5dbba6acd0eb8f92ac9278b9720a3aba8fcaef97716a26d8db47b68ee4201dca",
|
||||
"containsText": false,
|
||||
"containsDynamicText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
activity/cp/huwaa/assets/layers/panel-skin.webp
Normal file
|
After Width: | Height: | Size: 845 KiB |
BIN
activity/cp/huwaa/assets/layers/podium-bronze.webp
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
activity/cp/huwaa/assets/layers/podium-gold.webp
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
activity/cp/huwaa/assets/layers/podium-silver.webp
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
activity/cp/huwaa/assets/layers/reward-frame.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
activity/cp/huwaa/assets/layers/score-heart.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
activity/cp/huwaa/assets/layers/tab-active.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
activity/cp/huwaa/assets/layers/tab-inactive.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
832
activity/cp/huwaa/huwaa.css
Normal file
@ -0,0 +1,832 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--huwaa-cp-page-width: 375px;
|
||||
--huwaa-cp-design-scale: 0.3472222222;
|
||||
--huwaa-cp-brand: var(--hy-theme-primary-strong, #c52d45);
|
||||
--huwaa-cp-brand-deep: #a1152f;
|
||||
--huwaa-cp-text: #a11626;
|
||||
--huwaa-cp-muted: #dd708d;
|
||||
--huwaa-cp-row: rgba(197, 45, 69, 0.13);
|
||||
--huwaa-cp-page-bg: var(--hy-theme-bg, #f8c7d5);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: none;
|
||||
background: var(--huwaa-cp-page-bg);
|
||||
color: var(--hy-theme-text, #21182b);
|
||||
font-family:
|
||||
Inter, Arial, 'Helvetica Neue', Helvetica, 'Noto Sans', sans-serif;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar,
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.huwaa-cp-sr-only {
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
padding: 0 !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
clip: rect(0, 0, 0, 0) !important;
|
||||
white-space: nowrap !important;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.huwaa-cp-viewport,
|
||||
.huwaa-cp-page,
|
||||
.huwaa-cp-stage-wrap {
|
||||
position: relative;
|
||||
width: var(--huwaa-cp-page-width);
|
||||
max-width: 768px;
|
||||
min-height: 1297px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
background: var(--huwaa-cp-page-bg);
|
||||
}
|
||||
|
||||
.huwaa-cp-stage-wrap {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.huwaa-cp-stage {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
min-height: 3733px;
|
||||
overflow: hidden;
|
||||
direction: ltr;
|
||||
transform: scale(var(--huwaa-cp-design-scale));
|
||||
transform-origin: top left;
|
||||
background: #e98c9b;
|
||||
}
|
||||
|
||||
.huwaa-cp-stage > * {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.huwaa-cp-background {
|
||||
top: 489px;
|
||||
left: -487px;
|
||||
z-index: 0;
|
||||
width: 2415px;
|
||||
height: 4293px;
|
||||
opacity: 0.5;
|
||||
mix-blend-mode: screen;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.huwaa-cp-hero {
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 1080px;
|
||||
height: 1333px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.huwaa-cp-hero > img {
|
||||
width: 1080px;
|
||||
height: 1333px;
|
||||
object-fit: cover;
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
#000 0,
|
||||
#000 88.93%,
|
||||
transparent 100%
|
||||
);
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
#000 0,
|
||||
#000 88.93%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.huwaa-cp-wordmark {
|
||||
position: absolute;
|
||||
top: 856px;
|
||||
left: 20px;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
width: 1040px;
|
||||
min-height: 370px;
|
||||
margin: 0;
|
||||
place-items: center;
|
||||
color: #ffe0b2;
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 181px;
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
letter-spacing: -8px;
|
||||
line-height: 0.82;
|
||||
overflow-wrap: normal;
|
||||
paint-order: stroke fill;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
0 8px 0 #7f1d17,
|
||||
0 13px 22px rgba(91, 10, 25, 0.66),
|
||||
0 0 12px rgba(255, 248, 212, 0.78);
|
||||
text-wrap: balance;
|
||||
-webkit-text-stroke: 7px #b75f31;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.huwaa-cp-countdown {
|
||||
top: 1333px;
|
||||
left: 52px;
|
||||
z-index: 3;
|
||||
width: 974px;
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 223px;
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit:nth-child(1) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit:nth-child(2) {
|
||||
left: 250px;
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit:nth-child(3) {
|
||||
left: 501px;
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit:nth-child(4) {
|
||||
left: 756px;
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 163px;
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit strong {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 163px;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: 900;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-shadow:
|
||||
0 2px 0 rgba(176, 47, 78, 0.52),
|
||||
0 0 12px rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.huwaa-cp-time-unit span {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 194px;
|
||||
width: 31px;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: 900;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 0 rgba(176, 47, 78, 0.52);
|
||||
}
|
||||
|
||||
.huwaa-cp-tabs {
|
||||
top: 1495px;
|
||||
left: 39px;
|
||||
z-index: 5;
|
||||
display: grid;
|
||||
width: 1000px;
|
||||
height: 126px;
|
||||
grid-template-columns: repeat(3, 303px);
|
||||
column-gap: 46px;
|
||||
}
|
||||
|
||||
.huwaa-cp-tab {
|
||||
position: relative;
|
||||
width: 303px;
|
||||
height: 126px;
|
||||
color: rgba(161, 22, 52, 0.34);
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 1px rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.huwaa-cp-tab img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 303px;
|
||||
height: 126px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.huwaa-cp-tab span {
|
||||
position: absolute;
|
||||
top: 34px;
|
||||
left: 20px;
|
||||
width: 263px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
html[lang='es'] .huwaa-cp-tab[data-tab='rewards'] {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.huwaa-cp-tab::after {
|
||||
position: absolute;
|
||||
inset: -12px 0;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.huwaa-cp-tab.is-active {
|
||||
color: #ff4766;
|
||||
text-shadow:
|
||||
0 2px 0 rgba(255, 255, 255, 0.9),
|
||||
0 0 10px rgba(255, 139, 166, 0.36);
|
||||
}
|
||||
|
||||
.huwaa-cp-tab:focus-visible {
|
||||
border-radius: 58px;
|
||||
box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.huwaa-cp-tab-content {
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 4;
|
||||
width: 1080px;
|
||||
}
|
||||
|
||||
.huwaa-cp-copyright {
|
||||
left: 114px;
|
||||
z-index: 4;
|
||||
width: 850px;
|
||||
height: 30px;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 8px rgba(163, 48, 76, 0.3);
|
||||
}
|
||||
|
||||
.huwaa-cp-panel {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
width: 960px;
|
||||
border: 84px solid transparent;
|
||||
border-image-source: url('./assets/layers/panel-skin.webp');
|
||||
border-image-slice: 110 fill;
|
||||
border-image-width: 84px;
|
||||
border-image-repeat: stretch;
|
||||
color: var(--huwaa-cp-text);
|
||||
}
|
||||
|
||||
.huwaa-cp-panel-body {
|
||||
position: absolute;
|
||||
inset: -84px;
|
||||
}
|
||||
|
||||
.huwaa-cp-panel-title,
|
||||
.huwaa-cp-panel-subtitle {
|
||||
position: absolute;
|
||||
left: 55px;
|
||||
width: 850px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.huwaa-cp-panel-title {
|
||||
top: 75px;
|
||||
min-height: 45px;
|
||||
color: #b82e2d;
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.huwaa-cp-panel-subtitle {
|
||||
top: 140px;
|
||||
min-height: 27px;
|
||||
color: var(--huwaa-cp-muted);
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.huwaa-cp-panel.is-gift-panel .huwaa-cp-panel-title {
|
||||
top: 75px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.huwaa-cp-panel.is-gift-panel .huwaa-cp-panel-subtitle {
|
||||
top: 166px;
|
||||
}
|
||||
|
||||
.huwaa-cp-list {
|
||||
position: absolute;
|
||||
top: 203px;
|
||||
left: 60px;
|
||||
display: grid;
|
||||
width: 840px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.huwaa-cp-panel.is-gift-panel .huwaa-cp-list {
|
||||
top: 229px;
|
||||
}
|
||||
|
||||
.huwaa-cp-row {
|
||||
position: relative;
|
||||
width: 840px;
|
||||
height: 199px;
|
||||
overflow: hidden;
|
||||
border-radius: 30px;
|
||||
background: var(--huwaa-cp-row);
|
||||
}
|
||||
|
||||
.huwaa-cp-avatar {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
width: 113px;
|
||||
height: 113px;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
border: 5px solid rgba(255, 235, 207, 0.96);
|
||||
border-radius: 50%;
|
||||
background: #f2b9c6;
|
||||
box-shadow:
|
||||
0 0 0 3px rgba(183, 81, 64, 0.48),
|
||||
0 6px 14px rgba(143, 25, 52, 0.18);
|
||||
color: #fff;
|
||||
font-size: 34px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.huwaa-cp-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.huwaa-cp-row .huwaa-cp-avatar.is-left {
|
||||
top: 26px;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.huwaa-cp-row .huwaa-cp-avatar.is-right {
|
||||
top: 26px;
|
||||
left: 669px;
|
||||
}
|
||||
|
||||
.huwaa-cp-name {
|
||||
position: absolute;
|
||||
top: 147px;
|
||||
z-index: 3;
|
||||
width: 144px;
|
||||
overflow: hidden;
|
||||
color: var(--huwaa-cp-text);
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.huwaa-cp-name.is-left {
|
||||
left: 42px;
|
||||
}
|
||||
|
||||
.huwaa-cp-name.is-right {
|
||||
left: 653px;
|
||||
}
|
||||
|
||||
.huwaa-cp-deco-line {
|
||||
position: absolute;
|
||||
top: 71px;
|
||||
z-index: 1;
|
||||
width: 159px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.huwaa-cp-deco-line.is-left {
|
||||
left: 336px;
|
||||
}
|
||||
|
||||
.huwaa-cp-deco-line.is-right {
|
||||
left: 504px;
|
||||
}
|
||||
|
||||
.huwaa-cp-gift {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 344px;
|
||||
z-index: 2;
|
||||
width: 152px;
|
||||
height: 152px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.huwaa-cp-score-heart {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 308px;
|
||||
z-index: 2;
|
||||
width: 220px;
|
||||
height: 143px;
|
||||
}
|
||||
|
||||
.huwaa-cp-score {
|
||||
position: absolute;
|
||||
top: 71px;
|
||||
left: 359px;
|
||||
z-index: 3;
|
||||
width: 120px;
|
||||
overflow: hidden;
|
||||
color: #ff4460;
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.huwaa-cp-state {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 110px;
|
||||
width: 740px;
|
||||
transform: translateY(-50%);
|
||||
color: var(--huwaa-cp-muted);
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
line-height: 42px;
|
||||
text-align: center;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.huwaa-cp-state.is-loading::before {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 14px;
|
||||
border: 4px solid rgba(197, 45, 69, 0.2);
|
||||
border-top-color: var(--huwaa-cp-brand);
|
||||
border-radius: 50%;
|
||||
content: '';
|
||||
animation: huwaa-cp-spin 0.8s linear infinite;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
.huwaa-cp-retry {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-width: 200px;
|
||||
height: 64px;
|
||||
margin: 22px auto 0;
|
||||
padding: 0 28px;
|
||||
border-radius: 32px;
|
||||
background: var(--hy-theme-button, #a868dc);
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.huwaa-cp-retry::after {
|
||||
position: absolute;
|
||||
inset: -43px -20px;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.huwaa-cp-retry:focus-visible {
|
||||
box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.82);
|
||||
}
|
||||
|
||||
.huwaa-cp-rank-podium {
|
||||
position: absolute;
|
||||
top: 1677px;
|
||||
left: 25px;
|
||||
width: 1030px;
|
||||
height: 751px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium-art,
|
||||
.huwaa-cp-podium-avatar,
|
||||
.huwaa-cp-podium-name {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium-art {
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium-avatar {
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: #efb2c1;
|
||||
color: #fff;
|
||||
font-weight: 900;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium-name {
|
||||
z-index: 3;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 2px 4px rgba(144, 33, 59, 0.36);
|
||||
white-space: nowrap;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-gold .huwaa-cp-podium-art {
|
||||
top: 0;
|
||||
left: 157px;
|
||||
width: 716px;
|
||||
height: 434px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-gold .huwaa-cp-podium-avatar {
|
||||
top: 119px;
|
||||
width: 223px;
|
||||
height: 223px;
|
||||
font-size: 54px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-gold .huwaa-cp-podium-avatar.is-left {
|
||||
left: 236px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-gold .huwaa-cp-podium-avatar.is-right {
|
||||
left: 557px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-gold .huwaa-cp-podium-name {
|
||||
top: 386px;
|
||||
width: 315px;
|
||||
height: 50px;
|
||||
background: rgba(188, 30, 65, 0.26);
|
||||
font-size: 35px;
|
||||
line-height: 49px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-gold .huwaa-cp-podium-name.is-left {
|
||||
left: 219px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-gold .huwaa-cp-podium-name.is-right {
|
||||
left: 497px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-art,
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-art {
|
||||
top: 467px;
|
||||
width: 500px;
|
||||
height: 284px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-art {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-art {
|
||||
top: 465px;
|
||||
left: 530px;
|
||||
height: 286px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-avatar,
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-avatar {
|
||||
top: 550px;
|
||||
width: 153px;
|
||||
height: 153px;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-avatar.is-left {
|
||||
left: 62px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-avatar.is-right {
|
||||
left: 281px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-avatar.is-left {
|
||||
left: 598px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-avatar.is-right {
|
||||
left: 812px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-name,
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-name {
|
||||
top: 716px;
|
||||
width: 172px;
|
||||
height: 34px;
|
||||
background: rgba(188, 30, 65, 0.22);
|
||||
font-size: 24px;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-name.is-left {
|
||||
left: 94px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-silver .huwaa-cp-podium-name.is-right {
|
||||
left: 281px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-name.is-left {
|
||||
left: 624px;
|
||||
}
|
||||
|
||||
.huwaa-cp-podium.is-bronze .huwaa-cp-podium-name.is-right {
|
||||
left: 811px;
|
||||
}
|
||||
|
||||
.huwaa-cp-rank-empty {
|
||||
position: absolute;
|
||||
top: 1990px;
|
||||
left: 170px;
|
||||
z-index: 4;
|
||||
width: 740px;
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
line-height: 42px;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 8px rgba(161, 21, 47, 0.42);
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-panel .huwaa-cp-panel-body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-crown {
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
left: 399px;
|
||||
width: 162px;
|
||||
height: 144px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-grid {
|
||||
position: absolute;
|
||||
top: 260px;
|
||||
left: 81px;
|
||||
display: grid;
|
||||
width: 800px;
|
||||
height: 604px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
grid-template-columns: repeat(3, 200px);
|
||||
grid-auto-rows: 272px;
|
||||
column-gap: 100px;
|
||||
row-gap: 60px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-grid::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-card {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 272px;
|
||||
color: var(--huwaa-cp-text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-icon {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
border-radius: 26px;
|
||||
background: rgba(197, 45, 69, 0.11);
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-frame,
|
||||
.huwaa-cp-reward-cover {
|
||||
position: absolute;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-frame {
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-cover {
|
||||
top: 48px;
|
||||
left: 48px;
|
||||
z-index: 1;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-name {
|
||||
display: -webkit-box;
|
||||
max-height: 60px;
|
||||
min-height: 36px;
|
||||
margin-top: 19px;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.huwaa-cp-reward-note {
|
||||
display: -webkit-box;
|
||||
max-height: 44px;
|
||||
margin-top: -2px;
|
||||
color: var(--huwaa-cp-muted);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
@keyframes huwaa-cp-spin {
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.huwaa-cp-state.is-loading::before {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
964
activity/cp/huwaa/huwaa.js
Normal file
@ -0,0 +1,964 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var DESIGN_WIDTH = 1080;
|
||||
var MAX_PAGE_WIDTH = 768;
|
||||
var ASSET_BASE = './huwaa/assets/layers/';
|
||||
var PODIUM_ASSETS = {
|
||||
gold: 'podium-gold.webp',
|
||||
silver: 'podium-silver.webp',
|
||||
bronze: 'podium-bronze.webp',
|
||||
};
|
||||
var CROWN_ASSETS = {
|
||||
1: 'crown-gold.webp',
|
||||
2: 'crown-silver.webp',
|
||||
3: 'crown-bronze.webp',
|
||||
};
|
||||
var STAGE_HEIGHTS = {
|
||||
plaza: 4283,
|
||||
rank: 3733,
|
||||
rewards: 4782,
|
||||
};
|
||||
var PANEL_LAYOUT = {
|
||||
plazaFirstTop: 1677,
|
||||
plazaFirstHeight: 1168,
|
||||
plazaSecondTop: 2904,
|
||||
plazaSecondHeight: 1142,
|
||||
rankPanelTop: 2484,
|
||||
rankPanelHeight: 1142,
|
||||
rewardFirstTop: 1677,
|
||||
rewardPanelHeight: 960,
|
||||
rewardPanelGap: 56,
|
||||
};
|
||||
|
||||
var viewport = document.getElementById('huwaaCpViewport');
|
||||
var page = document.getElementById('huwaaCpPage');
|
||||
var stage = document.getElementById('huwaaCpStage');
|
||||
var stageWrap = document.getElementById('huwaaCpStageWrap');
|
||||
var tabContent = document.getElementById('huwaaCpTabContent');
|
||||
var copyright = document.querySelector('.huwaa-cp-copyright');
|
||||
var announcement = document.getElementById('huwaaCpAnnouncement');
|
||||
var tabs = Array.prototype.slice.call(
|
||||
document.querySelectorAll('.huwaa-cp-tab')
|
||||
);
|
||||
|
||||
var requestGeneration = 0;
|
||||
var countdownEndAt = 0;
|
||||
var countdownTimer = 0;
|
||||
var serverTimeOffsetMS = 0;
|
||||
var activeTab = resolveInitialTab();
|
||||
var activityData = {
|
||||
statusState: 'loading',
|
||||
plazaState: 'loading',
|
||||
rank: [],
|
||||
previousRows: [],
|
||||
plazaRows: [],
|
||||
rewards: [],
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
function t(key, fallback) {
|
||||
if (window.HyAppI18n && window.HyAppI18n.t) {
|
||||
return window.HyAppI18n.t(key, fallback);
|
||||
}
|
||||
return fallback || key;
|
||||
}
|
||||
|
||||
function escapeHTML(value) {
|
||||
return String(value == null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function safeImageURL(value) {
|
||||
var source = String(value || '').trim();
|
||||
if (!source) return '';
|
||||
if (/^(https?:|data:image\/|blob:|\.\.?\/)/i.test(source)) {
|
||||
return escapeHTML(source);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function resolveInitialTab() {
|
||||
try {
|
||||
var requested = new URLSearchParams(window.location.search).get(
|
||||
'tab'
|
||||
);
|
||||
if (['plaza', 'rank', 'rewards'].indexOf(requested) >= 0) {
|
||||
return requested;
|
||||
}
|
||||
} catch (_) {
|
||||
// 无 URLSearchParams 的旧 WebView 使用设计稿默认的 CP Rank,不影响核心接口加载。
|
||||
}
|
||||
return 'rank';
|
||||
}
|
||||
|
||||
function isLocalRuntime() {
|
||||
try {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var hostname = String(window.location.hostname || '').toLowerCase();
|
||||
return (
|
||||
hostname === 'localhost' ||
|
||||
hostname === '127.0.0.1' ||
|
||||
hostname === '::1' ||
|
||||
params.get('env') === 'local'
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isExplicitMockMode() {
|
||||
try {
|
||||
return (
|
||||
isLocalRuntime() &&
|
||||
new URLSearchParams(window.location.search).get('mock') === '1'
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function twoDigits(value) {
|
||||
return String(Math.max(0, Number(value) || 0)).padStart(2, '0');
|
||||
}
|
||||
|
||||
function resolveExplicitEndAt() {
|
||||
if (!isLocalRuntime()) return 0;
|
||||
try {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var parsed = Date.parse(
|
||||
params.get('end_at') || params.get('endAt') || ''
|
||||
);
|
||||
return Number.isFinite(parsed) && parsed > Date.now() ? parsed : 0;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function nowMS() {
|
||||
return Date.now() + serverTimeOffsetMS;
|
||||
}
|
||||
|
||||
function getTimeParts() {
|
||||
var diff =
|
||||
countdownEndAt > 0 ? Math.max(0, countdownEndAt - nowMS()) : 0;
|
||||
var totalSeconds = Math.floor(diff / 1000);
|
||||
return {
|
||||
days: Math.floor(totalSeconds / 86400),
|
||||
hours: Math.floor((totalSeconds % 86400) / 3600),
|
||||
minutes: Math.floor((totalSeconds % 3600) / 60),
|
||||
seconds: totalSeconds % 60,
|
||||
};
|
||||
}
|
||||
|
||||
function updateCountdown() {
|
||||
var parts = getTimeParts();
|
||||
Object.keys(parts).forEach(function (key) {
|
||||
document
|
||||
.querySelectorAll('[data-count="' + key + '"]')
|
||||
.forEach(function (node) {
|
||||
node.textContent = twoDigits(parts[key]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initials(name) {
|
||||
var normalized = String(name || '').trim();
|
||||
return normalized ? normalized.charAt(0).toUpperCase() : 'CP';
|
||||
}
|
||||
|
||||
function renderAvatar(className, avatar, name) {
|
||||
var imageURL = safeImageURL(avatar);
|
||||
return [
|
||||
'<span class="huwaa-cp-avatar ',
|
||||
className,
|
||||
'" aria-hidden="true">',
|
||||
imageURL
|
||||
? '<img src="' + imageURL + '" alt="" loading="lazy" />'
|
||||
: '<span>' + escapeHTML(initials(name)) + '</span>',
|
||||
'</span>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderPodiumAvatar(className, avatar, name) {
|
||||
var imageURL = safeImageURL(avatar);
|
||||
return [
|
||||
'<span class="huwaa-cp-podium-avatar ',
|
||||
className,
|
||||
'" aria-hidden="true">',
|
||||
imageURL
|
||||
? '<img src="' + imageURL + '" alt="" loading="lazy" />'
|
||||
: '<span>' + escapeHTML(initials(name)) + '</span>',
|
||||
'</span>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderState(state, retryScope) {
|
||||
var key =
|
||||
state === 'error'
|
||||
? 'cp.error'
|
||||
: state === 'loading'
|
||||
? 'cp.loading'
|
||||
: state === 'disabled'
|
||||
? 'cp.disabled'
|
||||
: state === 'unavailable'
|
||||
? 'cp.unavailable'
|
||||
: 'cp.empty';
|
||||
var fallback =
|
||||
state === 'error'
|
||||
? 'Unable to load activity data.'
|
||||
: state === 'loading'
|
||||
? 'Loading...'
|
||||
: state === 'disabled'
|
||||
? 'This activity is not available.'
|
||||
: state === 'unavailable'
|
||||
? 'No data source is available yet.'
|
||||
: 'No data yet.';
|
||||
return [
|
||||
'<div class="huwaa-cp-state ',
|
||||
state === 'loading' ? 'is-loading' : '',
|
||||
'" role="status" aria-live="polite">',
|
||||
'<span>',
|
||||
escapeHTML(t(key, fallback)),
|
||||
'</span>',
|
||||
retryScope
|
||||
? '<button class="huwaa-cp-retry" type="button" data-retry="' +
|
||||
escapeHTML(retryScope) +
|
||||
'">' +
|
||||
escapeHTML(t('cp.retry', 'Retry')) +
|
||||
'</button>'
|
||||
: '',
|
||||
'</div>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderPanel(options) {
|
||||
var classes = ['huwaa-cp-panel'];
|
||||
if (options.className) classes.push(options.className);
|
||||
return [
|
||||
'<section class="',
|
||||
classes.join(' '),
|
||||
'"',
|
||||
options.ariaLabel
|
||||
? ' aria-label="' + escapeHTML(options.ariaLabel) + '"'
|
||||
: '',
|
||||
' style="top:',
|
||||
options.top,
|
||||
'px;height:',
|
||||
options.height,
|
||||
'px">',
|
||||
'<div class="huwaa-cp-panel-body">',
|
||||
options.body,
|
||||
'</div>',
|
||||
'</section>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderPanelHeader(
|
||||
titleKey,
|
||||
titleFallback,
|
||||
subtitleKey,
|
||||
subtitleFallback
|
||||
) {
|
||||
return [
|
||||
'<h2 class="huwaa-cp-panel-title">',
|
||||
escapeHTML(t(titleKey, titleFallback)),
|
||||
'</h2>',
|
||||
subtitleKey
|
||||
? '<p class="huwaa-cp-panel-subtitle">' +
|
||||
escapeHTML(t(subtitleKey, subtitleFallback)) +
|
||||
'</p>'
|
||||
: '',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderCPRow(row) {
|
||||
var center =
|
||||
row.type === 'gift'
|
||||
? '<img class="huwaa-cp-gift" src="' +
|
||||
ASSET_BASE +
|
||||
'gift-plane.webp" alt="" aria-hidden="true" />'
|
||||
: [
|
||||
'<img class="huwaa-cp-score-heart" src="',
|
||||
ASSET_BASE,
|
||||
'score-heart.webp" alt="" aria-hidden="true" />',
|
||||
'<strong class="huwaa-cp-score">',
|
||||
escapeHTML(row.score),
|
||||
'</strong>',
|
||||
].join('');
|
||||
return [
|
||||
'<article class="huwaa-cp-row">',
|
||||
renderAvatar('is-left', row.leftAvatar, row.leftName),
|
||||
renderAvatar('is-right', row.rightAvatar, row.rightName),
|
||||
'<span class="huwaa-cp-name is-left">',
|
||||
escapeHTML(row.leftName),
|
||||
'</span>',
|
||||
'<span class="huwaa-cp-name is-right">',
|
||||
escapeHTML(row.rightName),
|
||||
'</span>',
|
||||
'<img class="huwaa-cp-deco-line is-left" src="',
|
||||
ASSET_BASE,
|
||||
'deco-line-left.webp" alt="" aria-hidden="true" />',
|
||||
'<img class="huwaa-cp-deco-line is-right" src="',
|
||||
ASSET_BASE,
|
||||
'deco-line-right.webp" alt="" aria-hidden="true" />',
|
||||
center,
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function rowsOrState(rows, state, retryScope) {
|
||||
if (rows.length) {
|
||||
return (
|
||||
'<div class="huwaa-cp-list">' +
|
||||
rows.map(renderCPRow).join('') +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
return renderState(
|
||||
state === 'ready' ? 'empty' : state,
|
||||
state === 'error' ? retryScope : ''
|
||||
);
|
||||
}
|
||||
|
||||
function renderPreviousPanel(top, height) {
|
||||
var body =
|
||||
renderPanelHeader(
|
||||
'cp.previousWeekTopTitle',
|
||||
'Previous Top 3',
|
||||
'cp.previousWeekTopSubtitle',
|
||||
'(Top CPs from the previous completed week.)'
|
||||
) +
|
||||
rowsOrState(
|
||||
activityData.previousRows,
|
||||
activityData.statusState,
|
||||
'status'
|
||||
);
|
||||
return renderPanel({ top: top, height: height, body: body });
|
||||
}
|
||||
|
||||
function renderPlaza() {
|
||||
var firstBody =
|
||||
renderPanelHeader(
|
||||
'cp.huwaaPlazaGiftTitle',
|
||||
'Gifts exchanged between CPs ≥ 5000 gold will be displayed here',
|
||||
'cp.plazaGiftSubtitle',
|
||||
'(Display the last 10 records, sorted in ascending order.)'
|
||||
) +
|
||||
rowsOrState(activityData.plazaRows, activityData.plazaState, '');
|
||||
return [
|
||||
renderPanel({
|
||||
top: PANEL_LAYOUT.plazaFirstTop,
|
||||
height: PANEL_LAYOUT.plazaFirstHeight,
|
||||
className: 'is-gift-panel',
|
||||
body: firstBody,
|
||||
}),
|
||||
renderPreviousPanel(
|
||||
PANEL_LAYOUT.plazaSecondTop,
|
||||
PANEL_LAYOUT.plazaSecondHeight
|
||||
),
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderPodiumPair(item, place) {
|
||||
if (!item) return '';
|
||||
var level = place === 1 ? 'gold' : place === 2 ? 'silver' : 'bronze';
|
||||
var accessibleLabel = [
|
||||
t('cp.rankPlace', 'Rank'),
|
||||
String(place),
|
||||
item.leftName,
|
||||
item.rightName,
|
||||
].join(' ');
|
||||
return [
|
||||
'<article class="huwaa-cp-podium is-',
|
||||
level,
|
||||
'" aria-label="',
|
||||
escapeHTML(accessibleLabel),
|
||||
'">',
|
||||
renderPodiumAvatar('is-left', item.leftAvatar, item.leftName),
|
||||
renderPodiumAvatar('is-right', item.rightAvatar, item.rightName),
|
||||
'<img class="huwaa-cp-podium-art" src="',
|
||||
ASSET_BASE,
|
||||
PODIUM_ASSETS[level],
|
||||
'" alt="" aria-hidden="true" />',
|
||||
'<span class="huwaa-cp-podium-name is-left">',
|
||||
escapeHTML(item.leftName),
|
||||
'</span>',
|
||||
'<span class="huwaa-cp-podium-name is-right">',
|
||||
escapeHTML(item.rightName),
|
||||
'</span>',
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderRank() {
|
||||
var podium = '';
|
||||
if (activityData.rank.length) {
|
||||
var rankAt = function (place) {
|
||||
return activityData.rank.find(function (item) {
|
||||
return item.place === place;
|
||||
});
|
||||
};
|
||||
podium = [
|
||||
'<section class="huwaa-cp-rank-podium" aria-label="',
|
||||
escapeHTML(t('cp.rankPodiumLabel', 'Top CP ranking')),
|
||||
'">',
|
||||
renderPodiumPair(rankAt(1), 1),
|
||||
renderPodiumPair(rankAt(2), 2),
|
||||
renderPodiumPair(rankAt(3), 3),
|
||||
'</section>',
|
||||
].join('');
|
||||
} else {
|
||||
var rankStateKey =
|
||||
activityData.statusState === 'error'
|
||||
? 'cp.error'
|
||||
: activityData.statusState === 'loading'
|
||||
? 'cp.loading'
|
||||
: activityData.statusState === 'disabled'
|
||||
? 'cp.disabled'
|
||||
: 'cp.empty';
|
||||
var rankStateFallback =
|
||||
activityData.statusState === 'error'
|
||||
? 'Unable to load activity data.'
|
||||
: activityData.statusState === 'loading'
|
||||
? 'Loading...'
|
||||
: activityData.statusState === 'disabled'
|
||||
? 'This activity is not available.'
|
||||
: 'No data yet.';
|
||||
podium = [
|
||||
'<p class="huwaa-cp-rank-empty" role="status" aria-live="polite">',
|
||||
escapeHTML(t(rankStateKey, rankStateFallback)),
|
||||
'</p>',
|
||||
].join('');
|
||||
}
|
||||
return (
|
||||
podium +
|
||||
renderPreviousPanel(
|
||||
PANEL_LAYOUT.rankPanelTop,
|
||||
PANEL_LAYOUT.rankPanelHeight
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function rewardName(item) {
|
||||
var resourceName =
|
||||
item.resourceName ||
|
||||
item.name ||
|
||||
item.resource_name ||
|
||||
localizedResourceType(item.resource_type || item.resourceType) ||
|
||||
t('cp.reward', 'Reward');
|
||||
var duration = Number(item.duration_days || item.durationDays || 0);
|
||||
var quantity = Number(item.quantity || 0);
|
||||
if (duration > 0) {
|
||||
return (
|
||||
resourceName + '*' + duration + ' ' + t('cp.daysLong', 'days')
|
||||
);
|
||||
}
|
||||
if (quantity > 0) return resourceName + '*' + quantity;
|
||||
return resourceName;
|
||||
}
|
||||
|
||||
function localizedResourceType(value) {
|
||||
var type = String(value || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
var keys = {
|
||||
avatar_frame: ['cp.rewardAvatarFrame', 'Avatar frame'],
|
||||
profile_card: ['cp.rewardProfileCard', 'Profile card'],
|
||||
coin: ['cp.rewardCoins', 'Coins'],
|
||||
vehicle: ['cp.rewardVehicle', 'Vehicle'],
|
||||
chat_bubble: ['cp.rewardChatBubble', 'Chat bubble'],
|
||||
badge: ['cp.rewardBadge', 'Badge'],
|
||||
floating_screen: ['cp.rewardFloatingScreen', 'Floating screen'],
|
||||
gift: ['cp.rewardGift', 'Gift'],
|
||||
mic_seat_icon: ['cp.rewardMicSeatIcon', 'Mic seat icon'],
|
||||
mic_seat_animation: [
|
||||
'cp.rewardMicSeatAnimation',
|
||||
'Mic seat animation',
|
||||
],
|
||||
emoji_pack: ['cp.rewardEmojiPack', 'Emoji pack'],
|
||||
vip_trial_card: ['cp.rewardVipTrialCard', 'VIP trial card'],
|
||||
};
|
||||
return keys[type] ? t(keys[type][0], keys[type][1]) : '';
|
||||
}
|
||||
|
||||
function rewardNote(item) {
|
||||
return String(item.description || item.note || '').trim();
|
||||
}
|
||||
|
||||
function renderRewardItem(item) {
|
||||
var cover = safeImageURL(item.coverUrl || item.cover_url);
|
||||
return [
|
||||
'<article class="huwaa-cp-reward-card">',
|
||||
'<div class="huwaa-cp-reward-icon">',
|
||||
cover
|
||||
? '<img class="huwaa-cp-reward-cover" src="' +
|
||||
cover +
|
||||
'" alt="" loading="lazy" />'
|
||||
: '',
|
||||
'<img class="huwaa-cp-reward-frame" src="',
|
||||
ASSET_BASE,
|
||||
'reward-frame.webp" alt="" aria-hidden="true" />',
|
||||
'</div>',
|
||||
'<div class="huwaa-cp-reward-name">',
|
||||
escapeHTML(item.displayName || rewardName(item)),
|
||||
'</div>',
|
||||
item.note || rewardNote(item)
|
||||
? '<div class="huwaa-cp-reward-note">' +
|
||||
escapeHTML(item.note || rewardNote(item)) +
|
||||
'</div>'
|
||||
: '',
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderRewardPanel(section, index) {
|
||||
var tierNo = Number(section.rankNo || index + 1);
|
||||
if (tierNo < 1 || tierNo > 3) tierNo = index + 1;
|
||||
var top =
|
||||
PANEL_LAYOUT.rewardFirstTop +
|
||||
index *
|
||||
(PANEL_LAYOUT.rewardPanelHeight + PANEL_LAYOUT.rewardPanelGap);
|
||||
var items = Array.isArray(section.items) ? section.items : [];
|
||||
var tierLabels = {
|
||||
1: t('cp.rewardTierGold', 'First-place rewards'),
|
||||
2: t('cp.rewardTierSilver', 'Second-place rewards'),
|
||||
3: t('cp.rewardTierBronze', 'Third-place rewards'),
|
||||
};
|
||||
var body = [
|
||||
'<img class="huwaa-cp-reward-crown" src="',
|
||||
ASSET_BASE,
|
||||
CROWN_ASSETS[tierNo] || CROWN_ASSETS[3],
|
||||
'" alt="" aria-hidden="true" />',
|
||||
items.length
|
||||
? '<div class="huwaa-cp-reward-grid">' +
|
||||
items.map(renderRewardItem).join('') +
|
||||
'</div>'
|
||||
: renderState('empty', ''),
|
||||
].join('');
|
||||
return renderPanel({
|
||||
top: top,
|
||||
height: PANEL_LAYOUT.rewardPanelHeight,
|
||||
className: 'huwaa-cp-reward-panel',
|
||||
ariaLabel: tierLabels[tierNo],
|
||||
body: body,
|
||||
});
|
||||
}
|
||||
|
||||
function renderRewards() {
|
||||
if (!activityData.rewards.length) {
|
||||
var state =
|
||||
activityData.statusState === 'ready'
|
||||
? 'empty'
|
||||
: activityData.statusState;
|
||||
return renderPanel({
|
||||
top: PANEL_LAYOUT.rewardFirstTop,
|
||||
height: PANEL_LAYOUT.rewardPanelHeight,
|
||||
className: 'huwaa-cp-reward-panel',
|
||||
body: renderState(state, state === 'error' ? 'status' : ''),
|
||||
});
|
||||
}
|
||||
return [1, 2, 3]
|
||||
.map(function (rankNo) {
|
||||
return (
|
||||
activityData.rewards.find(function (section) {
|
||||
return section.rankNo === rankNo;
|
||||
}) || { rankNo: rankNo, items: [] }
|
||||
);
|
||||
})
|
||||
.map(renderRewardPanel)
|
||||
.join('');
|
||||
}
|
||||
|
||||
function formatScore(value) {
|
||||
var number = Number(value || 0);
|
||||
if (!Number.isFinite(number)) return '0';
|
||||
try {
|
||||
return number.toLocaleString(document.documentElement.lang || 'en');
|
||||
} catch (_) {
|
||||
return String(number);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePairUser(user) {
|
||||
var source = user || {};
|
||||
var userID = source.user_id || source.userId || '';
|
||||
var displayID = source.display_user_id || source.displayUserId || '';
|
||||
var name = source.username || source.name || displayID || userID || '';
|
||||
if (!name && !source.avatar) return null;
|
||||
return {
|
||||
name: name || 'CP',
|
||||
avatar: source.avatar || source.avatar_url || '',
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRankEntry(entry, fallbackPlace) {
|
||||
var source = entry || {};
|
||||
var userA = normalizePairUser(source.user_a || source.userA);
|
||||
var userB = normalizePairUser(source.user_b || source.userB);
|
||||
if (!userA || !userB) return null;
|
||||
return {
|
||||
type: 'score',
|
||||
place: Number(source.rank || fallbackPlace || 0),
|
||||
score: formatScore(source.score || source.intimacy_value || 0),
|
||||
leftName: userA.name,
|
||||
rightName: userB.name,
|
||||
leftAvatar: userA.avatar,
|
||||
rightAvatar: userB.avatar,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRewardSection(section, fallbackRankNo) {
|
||||
var source = section || {};
|
||||
var group = source.resource_group || source.resourceGroup || {};
|
||||
var groupItems = Array.isArray(group.items) ? group.items : [];
|
||||
var items = Array.isArray(source.items) ? source.items : [];
|
||||
return {
|
||||
rankNo: Number(
|
||||
source.rank_no || source.rankNo || fallbackRankNo || 0
|
||||
),
|
||||
items: items.map(function (item, index) {
|
||||
var groupItem = groupItems[index] || {};
|
||||
var resource = groupItem.resource || {};
|
||||
return {
|
||||
resourceName: String(resource.name || '').trim(),
|
||||
resource_type:
|
||||
item.resource_type ||
|
||||
item.resourceType ||
|
||||
resource.resource_type ||
|
||||
resource.resourceType ||
|
||||
'',
|
||||
duration_days: item.duration_days || item.durationDays || 0,
|
||||
quantity: item.quantity || groupItem.quantity || 0,
|
||||
coverUrl:
|
||||
item.cover_url ||
|
||||
item.coverUrl ||
|
||||
resource.preview_url ||
|
||||
resource.previewUrl ||
|
||||
resource.asset_url ||
|
||||
resource.assetUrl ||
|
||||
'',
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function applyActivityStatus(status) {
|
||||
var source = status || {};
|
||||
var serverTimeMS = Number(
|
||||
source.server_time_ms || source.serverTimeMS || 0
|
||||
);
|
||||
if (serverTimeMS > 0) {
|
||||
serverTimeOffsetMS = serverTimeMS - Date.now();
|
||||
}
|
||||
activityData.enabled = source.enabled !== false;
|
||||
if (!activityData.enabled) {
|
||||
countdownEndAt = 0;
|
||||
activityData.rank = [];
|
||||
activityData.previousRows = [];
|
||||
activityData.rewards = [];
|
||||
activityData.statusState = 'disabled';
|
||||
return;
|
||||
}
|
||||
var endMS = Number(
|
||||
(source.period && (source.period.end_ms || source.period.endMS)) ||
|
||||
source.end_ms ||
|
||||
source.endMS ||
|
||||
0
|
||||
);
|
||||
countdownEndAt = endMS > nowMS() ? endMS : 0;
|
||||
|
||||
activityData.rank = Array.isArray(source.entries)
|
||||
? source.entries
|
||||
.map(function (entry, index) {
|
||||
return normalizeRankEntry(entry, index + 1);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort(function (left, right) {
|
||||
return left.place - right.place;
|
||||
})
|
||||
.slice(0, 3)
|
||||
: [];
|
||||
activityData.previousRows = Array.isArray(source.previous_entries)
|
||||
? source.previous_entries
|
||||
.map(function (entry, index) {
|
||||
return normalizeRankEntry(entry, index + 1);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.slice(0, 3)
|
||||
: [];
|
||||
activityData.rewards = Array.isArray(source.rewards)
|
||||
? source.rewards
|
||||
.map(function (section, index) {
|
||||
return normalizeRewardSection(section, index + 1);
|
||||
})
|
||||
.filter(function (section) {
|
||||
return section.rankNo >= 1 && section.rankNo <= 3;
|
||||
})
|
||||
.sort(function (left, right) {
|
||||
return left.rankNo - right.rankNo;
|
||||
})
|
||||
: [];
|
||||
activityData.statusState = 'ready';
|
||||
}
|
||||
|
||||
function applyMockData() {
|
||||
var pairs = [
|
||||
['Avery', 'Noor', 126580],
|
||||
['Mina', 'Elif', 118420],
|
||||
['Lina', 'Sofia', 109730],
|
||||
['Dara', 'Maya', 98760],
|
||||
];
|
||||
function pair(row, index, type) {
|
||||
return {
|
||||
type: type || 'score',
|
||||
place: index + 1,
|
||||
score: formatScore(row[2]),
|
||||
rawScore: row[2],
|
||||
leftName: row[0],
|
||||
rightName: row[1],
|
||||
leftAvatar: '',
|
||||
rightAvatar: '',
|
||||
};
|
||||
}
|
||||
activityData.rank = pairs.slice(0, 3).map(function (row, index) {
|
||||
return pair(row, index, 'score');
|
||||
});
|
||||
activityData.previousRows = pairs
|
||||
.slice(0, 3)
|
||||
.map(function (row, index) {
|
||||
return pair(row, index, 'score');
|
||||
});
|
||||
activityData.plazaRows = pairs.map(function (row, index) {
|
||||
return pair(row, index, 'gift');
|
||||
});
|
||||
activityData.rewards = ['gold', 'silver', 'bronze'].map(
|
||||
function (_, tier) {
|
||||
return {
|
||||
rankNo: tier + 1,
|
||||
items: [
|
||||
{
|
||||
displayName: t('cp.rewardMedal7', 'Medal*7 days'),
|
||||
note:
|
||||
tier === 0
|
||||
? t(
|
||||
'cp.rewardMedalNote',
|
||||
'(Permanent medal after getting 3 times)'
|
||||
)
|
||||
: '',
|
||||
},
|
||||
{
|
||||
displayName: t(
|
||||
'cp.rewardHonorBanner7',
|
||||
'Honor banner*7 days'
|
||||
),
|
||||
},
|
||||
{
|
||||
displayName: t(
|
||||
'cp.rewardHeadwear7',
|
||||
'Headwear*7days'
|
||||
),
|
||||
},
|
||||
{ displayName: t('cp.rewardMount7', 'Mount*7days') },
|
||||
{
|
||||
displayName: t(
|
||||
'cp.rewardCoins20000',
|
||||
'Coins*20000'
|
||||
),
|
||||
},
|
||||
{ displayName: t('cp.rewardDuke7', 'Duke*7days') },
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
activityData.statusState = 'ready';
|
||||
activityData.plazaState = 'ready';
|
||||
activityData.enabled = true;
|
||||
serverTimeOffsetMS = 0;
|
||||
countdownEndAt = Date.now() + 2 * 86400000 + 2 * 3600000 + 2 * 60000;
|
||||
}
|
||||
|
||||
function loadData(scope) {
|
||||
var generation = ++requestGeneration;
|
||||
if (isExplicitMockMode()) {
|
||||
applyMockData();
|
||||
render();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!scope || scope === 'status') {
|
||||
activityData.statusState = 'loading';
|
||||
activityData.rank = [];
|
||||
activityData.previousRows = [];
|
||||
activityData.rewards = [];
|
||||
}
|
||||
// 设计稿的 Plaza 是“单笔 CP 送礼事件流”;当前公开 intimacy-leaderboard 是累计亲密榜,
|
||||
// 两者排序和业务含义不同。在后端事件 feed 上线前保持明确不可用态,绝不把累计榜伪装成送礼记录。
|
||||
activityData.plazaState = 'unavailable';
|
||||
activityData.plazaRows = [];
|
||||
render();
|
||||
|
||||
var statusRequest =
|
||||
window.HyAppAPI && window.HyAppAPI.cpWeeklyRank
|
||||
? window.HyAppAPI.cpWeeklyRank.status({ limit: 3 })
|
||||
: Promise.reject(new Error('cp_weekly_rank_api_unavailable'));
|
||||
|
||||
// allSettled 统一承接未来可并行加入的 Plaza 事件 feed;generation 防止重试后的旧周榜响应覆盖新状态。
|
||||
return Promise.allSettled([statusRequest]).then(function (results) {
|
||||
if (generation !== requestGeneration) return;
|
||||
if (results[0].status === 'fulfilled') {
|
||||
applyActivityStatus(results[0].value);
|
||||
} else {
|
||||
activityData.statusState = 'error';
|
||||
}
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function applyTabState() {
|
||||
tabs.forEach(function (tab) {
|
||||
var isActive = tab.getAttribute('data-tab') === activeTab;
|
||||
tab.classList.toggle('is-active', isActive);
|
||||
tab.setAttribute('aria-selected', String(isActive));
|
||||
tab.setAttribute('tabindex', isActive ? '0' : '-1');
|
||||
var image = tab.querySelector('img');
|
||||
if (image) {
|
||||
image.src =
|
||||
ASSET_BASE +
|
||||
(isActive ? 'tab-active.webp' : 'tab-inactive.webp');
|
||||
}
|
||||
});
|
||||
var activeButton = tabs.find(function (tab) {
|
||||
return tab.getAttribute('data-tab') === activeTab;
|
||||
});
|
||||
tabContent.setAttribute(
|
||||
'aria-labelledby',
|
||||
activeButton ? activeButton.id : 'huwaaCpTabRank'
|
||||
);
|
||||
page.setAttribute('data-tab', activeTab);
|
||||
}
|
||||
|
||||
function getPageWidth() {
|
||||
var viewportWidth =
|
||||
window.innerWidth ||
|
||||
document.documentElement.clientWidth ||
|
||||
DESIGN_WIDTH;
|
||||
return Math.min(Math.max(viewportWidth, 1), MAX_PAGE_WIDTH);
|
||||
}
|
||||
|
||||
function updateStageHeight() {
|
||||
var designHeight = STAGE_HEIGHTS[activeTab] || STAGE_HEIGHTS.rank;
|
||||
// Rewards 的完整 4782px 高度仅对应三档奖励都存在的设计态。加载失败、活动关闭或确实为空时
|
||||
// 只有首块状态面板;按面板底部收拢页面,避免把不存在的二、三档内容伪装成长段空白。
|
||||
if (activeTab === 'rewards' && !activityData.rewards.length) {
|
||||
designHeight =
|
||||
PANEL_LAYOUT.rewardFirstTop +
|
||||
PANEL_LAYOUT.rewardPanelHeight +
|
||||
160;
|
||||
}
|
||||
var pageWidth = getPageWidth();
|
||||
var designScale = pageWidth / DESIGN_WIDTH;
|
||||
stage.style.height = designHeight + 'px';
|
||||
stage.style.minHeight = designHeight + 'px';
|
||||
document.documentElement.style.setProperty(
|
||||
'--huwaa-cp-page-width',
|
||||
pageWidth + 'px'
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
'--huwaa-cp-design-scale',
|
||||
String(designScale)
|
||||
);
|
||||
stageWrap.style.height = Math.ceil(designHeight * designScale) + 'px';
|
||||
stageWrap.style.minHeight = stageWrap.style.height;
|
||||
copyright.style.top = designHeight - 77 + 'px';
|
||||
document.body.style.minHeight = stageWrap.style.height;
|
||||
viewport.style.minHeight = stageWrap.style.height;
|
||||
page.style.minHeight = stageWrap.style.height;
|
||||
}
|
||||
|
||||
function render() {
|
||||
document.title = t('cp.pageTitle', 'Last Weekly CP');
|
||||
tabContent.innerHTML =
|
||||
activeTab === 'plaza'
|
||||
? renderPlaza()
|
||||
: activeTab === 'rewards'
|
||||
? renderRewards()
|
||||
: renderRank();
|
||||
applyTabState();
|
||||
updateStageHeight();
|
||||
updateCountdown();
|
||||
}
|
||||
|
||||
function updateURLTab() {
|
||||
try {
|
||||
var nextURL = new URL(window.location.href);
|
||||
nextURL.searchParams.set('tab', activeTab);
|
||||
window.history.replaceState(null, '', nextURL.toString());
|
||||
} catch (_) {
|
||||
// tab 切换本身不依赖 URL;history 被宿主禁用时仅放弃可分享状态参数。
|
||||
}
|
||||
}
|
||||
|
||||
function selectTab(nextTab, shouldFocus) {
|
||||
if (['plaza', 'rank', 'rewards'].indexOf(nextTab) < 0) return;
|
||||
activeTab = nextTab;
|
||||
updateURLTab();
|
||||
render();
|
||||
var activeButton = tabs.find(function (tab) {
|
||||
return tab.getAttribute('data-tab') === activeTab;
|
||||
});
|
||||
if (shouldFocus && activeButton) activeButton.focus();
|
||||
announcement.textContent =
|
||||
activeButton && activeButton.querySelector('span')
|
||||
? activeButton.querySelector('span').textContent
|
||||
: '';
|
||||
}
|
||||
|
||||
function bindInteractions() {
|
||||
tabs.forEach(function (tab, index) {
|
||||
tab.addEventListener('click', function () {
|
||||
selectTab(tab.getAttribute('data-tab') || 'rank', false);
|
||||
});
|
||||
tab.addEventListener('keydown', function (event) {
|
||||
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
|
||||
return;
|
||||
event.preventDefault();
|
||||
var delta = event.key === 'ArrowRight' ? 1 : -1;
|
||||
var nextIndex = (index + delta + tabs.length) % tabs.length;
|
||||
selectTab(tabs[nextIndex].getAttribute('data-tab'), true);
|
||||
});
|
||||
});
|
||||
tabContent.addEventListener('click', function (event) {
|
||||
var retry = event.target.closest('[data-retry]');
|
||||
if (retry) loadData(retry.getAttribute('data-retry') || 'status');
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
countdownEndAt = resolveExplicitEndAt();
|
||||
bindInteractions();
|
||||
render();
|
||||
loadData();
|
||||
countdownTimer = window.setInterval(updateCountdown, 1000);
|
||||
window.addEventListener('resize', updateStageHeight);
|
||||
window.addEventListener('hyapp:i18n-ready', function () {
|
||||
if (isExplicitMockMode()) applyMockData();
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
requestGeneration += 1;
|
||||
if (countdownTimer) window.clearInterval(countdownTimer);
|
||||
});
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
424
activity/invite/huwaa.html
Normal file
@ -0,0 +1,424 @@
|
||||
<!doctype html>
|
||||
<html lang="en" dir="ltr" data-hy-app-code="huwaa">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<title data-i18n="huwaa_invite.page_title">Cash Wheel</title>
|
||||
<link rel="icon" href="data:," />
|
||||
<link rel="stylesheet" href="../../common/theme.css?v=20260716" />
|
||||
<link rel="stylesheet" href="./huwaa/huwaa.css?v=20260716" />
|
||||
</head>
|
||||
<body>
|
||||
<main
|
||||
class="huwaa-page"
|
||||
id="huwaaPage"
|
||||
aria-label="Huwaa cash wheel invite activity"
|
||||
data-i18n-aria="huwaa_invite.page_aria"
|
||||
>
|
||||
<section class="huwaa-stage" id="huwaaStage">
|
||||
<div class="huwaa-palace" aria-hidden="true">
|
||||
<img
|
||||
src="./huwaa/assets/layers/bg-palace.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<img
|
||||
src="./huwaa/assets/layers/bg-palace.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="huwaa-status-bar" aria-hidden="true">
|
||||
<span class="huwaa-status-time">9:41</span>
|
||||
<img
|
||||
class="huwaa-status-cellular"
|
||||
src="./huwaa/assets/layers/status-cellular.svg"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="huwaa-status-wifi"
|
||||
src="./huwaa/assets/layers/status-wifi.svg"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="huwaa-status-battery"
|
||||
src="./huwaa/assets/layers/status-battery.svg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<header class="huwaa-nav">
|
||||
<button
|
||||
class="huwaa-nav-back"
|
||||
id="huwaaBackButton"
|
||||
type="button"
|
||||
aria-label="Back"
|
||||
data-i18n-aria="huwaa_invite.back"
|
||||
>
|
||||
<img src="./huwaa/assets/layers/nav-back.svg" alt="" />
|
||||
</button>
|
||||
<h1 data-i18n="huwaa_invite.page_title">Cash Wheel</h1>
|
||||
</header>
|
||||
|
||||
<section
|
||||
class="huwaa-wallet-card"
|
||||
aria-label="Cash wheel points"
|
||||
data-i18n-aria="huwaa_invite.wallet_aria"
|
||||
>
|
||||
<img
|
||||
class="huwaa-wallet-skin"
|
||||
src="./huwaa/assets/layers/panel-gold-emerald.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<div class="huwaa-wallet-texture" aria-hidden="true">
|
||||
<img
|
||||
src="./huwaa/assets/layers/wallet-inner-panel.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="huwaa-wallet-rate" aria-hidden="true">
|
||||
<img src="./huwaa/assets/layers/points-a.webp" alt="" />
|
||||
<strong id="huwaaRatePoints">90K</strong>
|
||||
<img
|
||||
class="huwaa-rate-exchange"
|
||||
src="./huwaa/assets/layers/icon-convert.svg"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="huwaa-rate-finance"
|
||||
src="./huwaa/assets/layers/icon-finance.svg"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="huwaa-rate-infinity"
|
||||
src="./huwaa/assets/layers/icon-infinity.svg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="huwaa-wallet-balance">
|
||||
<img src="./huwaa/assets/layers/points-a.webp" alt="" />
|
||||
<strong id="huwaaPointBalance">89,094</strong>
|
||||
</div>
|
||||
<strong class="huwaa-wallet-cash" id="huwaaCashValue"
|
||||
>≈$8.9</strong
|
||||
>
|
||||
<p class="huwaa-wallet-remaining">
|
||||
<span data-i18n="huwaa_invite.points_left"
|
||||
>Points Left To Withdraw:</span
|
||||
>
|
||||
<strong id="huwaaPointsLeft">1,000</strong>
|
||||
</p>
|
||||
<button
|
||||
class="huwaa-withdraw-button"
|
||||
id="huwaaWithdrawButton"
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/button-withdraw.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.withdraw"
|
||||
>WITHDRAW TO WALLET</span
|
||||
>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="huwaa-wheel-region"
|
||||
aria-label="Cash wheel"
|
||||
data-i18n-aria="huwaa_invite.wheel_aria"
|
||||
>
|
||||
<img
|
||||
class="huwaa-wheel-base"
|
||||
src="./huwaa/assets/layers/wheel-base.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
<button
|
||||
class="huwaa-reward-entry"
|
||||
id="huwaaRewardEntry"
|
||||
type="button"
|
||||
aria-label="Estimated rewards"
|
||||
data-i18n-aria="huwaa_invite.estimated_rewards"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-red.webp"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-red.webp"
|
||||
alt=""
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div class="huwaa-wheel-prizes" id="huwaaWheelPrizes">
|
||||
<button
|
||||
class="huwaa-prize"
|
||||
type="button"
|
||||
data-index="0"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-orange.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.medium">Medium</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-prize"
|
||||
type="button"
|
||||
data-index="1"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-green.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.medium">Medium</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-prize"
|
||||
type="button"
|
||||
data-index="2"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-white-gold.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.medium">Medium</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-prize"
|
||||
type="button"
|
||||
data-index="3"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-blue.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.medium">Medium</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-prize"
|
||||
type="button"
|
||||
data-index="4"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-purple.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.medium">Medium</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-prize"
|
||||
type="button"
|
||||
data-index="5"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-red.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.medium">Medium</span>
|
||||
</button>
|
||||
<button
|
||||
class="huwaa-prize"
|
||||
type="button"
|
||||
data-index="6"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/chest-gold.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.medium">Medium</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="huwaa-spin-button"
|
||||
id="huwaaSpinButton"
|
||||
type="button"
|
||||
aria-label="Spin"
|
||||
data-i18n-aria="huwaa_invite.spin"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/wheel-spin-center.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<span
|
||||
class="huwaa-spin-label"
|
||||
data-i18n="huwaa_invite.spin"
|
||||
>Spin</span
|
||||
>
|
||||
<span class="huwaa-spin-cost" id="huwaaSpinCost"
|
||||
>*5</span
|
||||
>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<button
|
||||
class="huwaa-invite-button"
|
||||
id="huwaaInviteButton"
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/button-invite.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.invite_now"
|
||||
>Invite Friends Now</span
|
||||
>
|
||||
</button>
|
||||
|
||||
<section
|
||||
class="huwaa-task-panel huwaa-extendable-panel"
|
||||
aria-label="Invitation tasks"
|
||||
data-i18n-aria="huwaa_invite.tasks_aria"
|
||||
>
|
||||
<div class="huwaa-extendable-skin" aria-hidden="true">
|
||||
<span class="huwaa-skin-top"></span>
|
||||
<span class="huwaa-skin-middle"></span>
|
||||
<span class="huwaa-skin-bottom"></span>
|
||||
</div>
|
||||
<div class="huwaa-task-list" id="huwaaTaskList"></div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="huwaa-win-modal"
|
||||
id="huwaaWinModal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="huwaaWinTitle"
|
||||
hidden
|
||||
>
|
||||
<button
|
||||
class="huwaa-modal-scrim"
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
data-i18n-aria="huwaa_invite.close"
|
||||
data-close-win
|
||||
></button>
|
||||
<article class="huwaa-win-dialog" aria-live="polite">
|
||||
<img
|
||||
class="huwaa-win-skin"
|
||||
src="./huwaa/assets/layers/reward-modal-panel.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<div class="huwaa-win-media">
|
||||
<img id="huwaaWinImage" alt="" />
|
||||
</div>
|
||||
<h2
|
||||
id="huwaaWinTitle"
|
||||
tabindex="-1"
|
||||
data-i18n="huwaa_invite.congratulations"
|
||||
>
|
||||
CONGRATULATIONS!
|
||||
</h2>
|
||||
<p
|
||||
class="huwaa-win-kicker"
|
||||
data-i18n="huwaa_invite.you_won"
|
||||
>
|
||||
You have won
|
||||
</p>
|
||||
<p class="huwaa-win-reward">
|
||||
<strong id="huwaaWinReward">Medium Reward</strong>
|
||||
<span id="huwaaWinPoints">(500 Points)</span>
|
||||
</p>
|
||||
<button
|
||||
class="huwaa-awesome-button"
|
||||
id="huwaaAwesomeButton"
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
src="./huwaa/assets/layers/button-awesome.webp"
|
||||
alt=""
|
||||
draggable="false"
|
||||
/>
|
||||
<span data-i18n="huwaa_invite.awesome"
|
||||
>AWESOME</span
|
||||
>
|
||||
</button>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="huwaa-reward-drawer"
|
||||
id="huwaaRewardDrawer"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="huwaaRewardTitle"
|
||||
hidden
|
||||
>
|
||||
<button
|
||||
class="huwaa-modal-scrim"
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
data-i18n-aria="huwaa_invite.close"
|
||||
data-close-rewards
|
||||
></button>
|
||||
<article class="huwaa-reward-sheet huwaa-extendable-panel">
|
||||
<div class="huwaa-extendable-skin" aria-hidden="true">
|
||||
<span class="huwaa-skin-top"></span>
|
||||
<span class="huwaa-skin-middle"></span>
|
||||
</div>
|
||||
<h2
|
||||
id="huwaaRewardTitle"
|
||||
tabindex="-1"
|
||||
data-i18n="huwaa_invite.estimated_rewards"
|
||||
>
|
||||
ESTIMATED REWARDS
|
||||
</h2>
|
||||
<div
|
||||
class="huwaa-estimated-grid"
|
||||
id="huwaaEstimatedGrid"
|
||||
></div>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<template id="huwaaTaskTemplate">
|
||||
<article class="huwaa-task-card">
|
||||
<img
|
||||
class="huwaa-task-icon"
|
||||
src="./huwaa/assets/layers/reward-calendar-check.webp"
|
||||
alt=""
|
||||
/>
|
||||
<div class="huwaa-task-copy">
|
||||
<strong data-task-title></strong>
|
||||
<span data-task-description></span>
|
||||
</div>
|
||||
<strong class="huwaa-task-reward" data-task-reward></strong>
|
||||
<span class="huwaa-task-status" data-task-status></span>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<template id="huwaaEstimatedTemplate">
|
||||
<article class="huwaa-estimated-item">
|
||||
<span class="huwaa-estimated-icon">
|
||||
<img alt="" />
|
||||
</span>
|
||||
<strong></strong>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
window.HyAppDefaultAppCode = 'huwaa';
|
||||
</script>
|
||||
<script src="../../common/i18n.js?v=20260716"></script>
|
||||
<script src="../../common/api.js?v=20260716"></script>
|
||||
<script src="../../common/params.js?v=20260716"></script>
|
||||
<script src="../../common/toast.js?v=20260716"></script>
|
||||
<script src="./huwaa/huwaa.js?v=20260716"></script>
|
||||
</body>
|
||||
</html>
|
||||
11
activity/invite/huwaa/assets/layers/bg-palace-mask.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 541 812" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="image 5044" opacity="0.5">
|
||||
<rect width="541" height="812" fill="url(#paint0_linear_0_18)" style="mix-blend-mode:screen"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_18" x1="270.5" y1="791.5" x2="270.5" y2="812" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 542 B |
BIN
activity/invite/huwaa/assets/layers/bg-palace.webp
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
activity/invite/huwaa/assets/layers/button-awesome.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
activity/invite/huwaa/assets/layers/button-invite.webp
Normal file
|
After Width: | Height: | Size: 408 KiB |
BIN
activity/invite/huwaa/assets/layers/button-withdraw.webp
Normal file
|
After Width: | Height: | Size: 366 KiB |
BIN
activity/invite/huwaa/assets/layers/chest-blue.webp
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
activity/invite/huwaa/assets/layers/chest-gold.webp
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
activity/invite/huwaa/assets/layers/chest-green.webp
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
activity/invite/huwaa/assets/layers/chest-orange.webp
Normal file
|
After Width: | Height: | Size: 174 KiB |
BIN
activity/invite/huwaa/assets/layers/chest-purple.webp
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
activity/invite/huwaa/assets/layers/chest-red.webp
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
activity/invite/huwaa/assets/layers/chest-white-gold.webp
Normal file
|
After Width: | Height: | Size: 171 KiB |
10
activity/invite/huwaa/assets/layers/icon-convert.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 14 12.5205" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="转换_转换_copy 1" clip-path="url(#clip0_0_6)">
|
||||
<path id="Vector" d="M0.0273838 7.72325L3.28198 12.4413C3.29831 12.4654 3.32031 12.4852 3.34605 12.4989C3.3718 12.5126 3.4005 12.5197 3.42965 12.5197C3.4588 12.5197 3.48751 12.5126 3.51325 12.4989C3.539 12.4852 3.561 12.4654 3.57732 12.4413L6.83413 7.72325C6.85226 7.69708 6.86227 7.66615 6.86293 7.63432C6.86358 7.60249 6.85485 7.57117 6.83781 7.54428C6.82029 7.5169 6.79554 7.49491 6.76629 7.48074C6.73704 7.46657 6.70444 7.46078 6.6721 7.464H4.35949V3.47216C4.35949 3.47216 4.30425 1.32084 5.94591 0.777304C5.35148 0.834823 4.77582 1.0169 4.25644 1.31168C3.73705 1.60646 3.28557 2.00734 2.93141 2.4882C2.64546 2.8983 2.49505 3.38763 2.50129 3.88755V7.43749H0.188678C0.155221 7.43884 0.122642 7.44859 0.0939403 7.46583C0.0652391 7.48308 0.041338 7.50727 0.0244378 7.53618C0.0080584 7.56471 -0.000375733 7.59711 1.28449e-05 7.63001C0.000401422 7.66291 0.00959851 7.69511 0.0266473 7.72325H0.0273838ZM13.9627 4.79566L10.7074 0.0783638C10.6909 0.0542118 10.6688 0.0344501 10.6429 0.020794C10.617 0.00713785 10.5882 0 10.559 0C10.5297 0 10.5009 0.00713785 10.4751 0.020794C10.4492 0.0344501 10.4271 0.0542118 10.4106 0.0783638L7.15745 4.79418C7.1394 4.82035 7.12936 4.85122 7.12857 4.88301C7.12779 4.9148 7.13629 4.94612 7.15303 4.97315C7.18839 5.02913 7.25246 5.0608 7.31875 5.05417H9.63136V9.04601C9.63136 9.04601 9.68439 11.1966 8.04347 11.7409C8.63772 11.6834 9.21321 11.5014 9.73247 11.2067C10.2517 10.9121 10.7031 10.5114 11.0572 10.0307C11.3435 9.61992 11.4939 9.12979 11.4873 8.62914V5.05711H13.8022C13.8685 5.06448 13.9333 5.03281 13.9686 4.9761C13.9858 4.9489 13.9944 4.91722 13.9935 4.88509C13.9926 4.85296 13.9821 4.82183 13.9635 4.79566H13.9627Z" fill="var(--fill-0, white)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_0_6">
|
||||
<rect width="14" height="12.5205" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
5
activity/invite/huwaa/assets/layers/icon-finance.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Finance 1">
|
||||
<path id="Vector" d="M5.23346 3.26063L4.44437 1.36108C5.02265 1.41856 5.60249 1.26808 6.07983 0.936627C6.20519 0.799172 6.37001 0.696082 6.55646 0.638172C6.74292 0.5809 6.94274 0.570082 7.13555 0.6089C7.23371 0.630091 7.32525 0.674814 7.40229 0.739219C7.47933 0.803624 7.53957 0.885783 7.57783 0.978627C7.60964 1.14535 7.72165 1.29045 7.88201 1.37635C8.22521 1.49318 8.60062 1.47009 8.92692 1.31208C9.08283 1.26372 9.37555 1.17145 9.41374 1.21599C9.3693 1.96612 9.13808 2.69324 8.7411 3.33126C8.20899 3.47896 7.65976 3.55598 7.10755 3.56035C6.47148 3.55355 5.83993 3.45254 5.23346 3.26063ZM12.2182 10.8881C12.1452 11.4216 11.899 11.9163 11.5176 12.2964C11.116 12.6968 10.6058 12.9707 10.0501 13.0842C8.04129 13.5314 5.95873 13.5314 3.94992 13.0842C3.39425 12.9707 2.88406 12.6968 2.48246 12.2964C2.10095 11.9164 1.85479 11.4216 1.78183 10.8881C1.42037 7.25572 4.24137 4.65299 4.99037 4.03063C5.66937 4.23935 6.38019 4.35008 7.09674 4.35772H7.10565C7.76752 4.35317 8.42575 4.2592 9.06246 4.07835C9.8821 4.76563 12.5707 7.31999 12.2188 10.8887H12.2182V10.8881ZM7.18455 8.29363C7.16737 8.29363 7.1591 8.28535 7.14065 8.28535C6.50619 7.99835 6.55901 7.83799 6.61246 7.69481C6.64958 7.60417 6.71411 7.52741 6.79701 7.47526C6.88409 7.42041 6.98491 7.39129 7.08783 7.39126H7.09674C7.19855 7.38657 7.29961 7.41084 7.38819 7.46126C7.47307 7.50929 7.54073 7.58274 7.58165 7.67126C7.67328 7.87426 7.92846 7.97354 8.15437 7.89399C8.26038 7.85818 8.34782 7.78173 8.39746 7.68145C8.42037 7.63379 8.43258 7.5817 8.43324 7.52882C8.4339 7.47595 8.42299 7.42357 8.40128 7.37535C8.25046 7.02535 7.92783 6.75935 7.52883 6.65626V6.34508C7.52819 6.1249 7.33155 5.94672 7.08783 5.94608C6.97265 5.94419 6.86095 5.98551 6.77474 6.0619C6.73462 6.09735 6.70252 6.14094 6.68057 6.18977C6.65863 6.2386 6.64734 6.29155 6.64746 6.34508V6.66454C6.23637 6.78672 5.9131 7.07626 5.77437 7.44726C5.42183 8.41326 6.45337 8.86826 6.7881 9.01208C6.80592 9.01972 6.81483 9.02799 6.83265 9.02799C7.47537 9.31499 7.62555 9.45117 7.56383 9.69044C7.51928 9.85844 7.29019 9.92972 7.10565 9.94626H7.04392C6.91437 9.95771 6.78411 9.93442 6.66655 9.87881C6.55317 9.82572 6.45766 9.74082 6.39164 9.63445C6.29683 9.43272 6.04037 9.33663 5.81637 9.41999C5.59174 9.50272 5.48292 9.73372 5.57201 9.93799C5.76419 10.3421 6.16892 10.6304 6.64746 10.7048V10.8639C6.64725 10.9175 6.65849 10.9706 6.68044 11.0196C6.70239 11.0685 6.73454 11.1122 6.77474 11.1477C6.85746 11.2228 6.97074 11.2648 7.08783 11.2635C7.33092 11.2629 7.52819 11.0847 7.52883 10.8645V10.6724C7.74201 10.6265 7.93801 10.5285 8.09519 10.3892C8.25174 10.2504 8.36374 10.0748 8.4191 9.88199C8.66601 8.94781 7.7401 8.54117 7.18455 8.29363Z" fill="var(--fill-0, #ECC100)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
10
activity/invite/huwaa/assets/layers/icon-infinity.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="符号_无限 1" clip-path="url(#clip0_0_4)">
|
||||
<path id="Vector" d="M10.7713 3.58594C9.47625 3.58594 8.32125 4.32969 7.67375 5.56344C7.51625 5.86094 7.63875 6.23719 7.93625 6.38594C8.23375 6.54344 8.61 6.42094 8.75875 6.12344C9.1875 5.30094 9.93125 4.80219 10.7625 4.80219C11.865 4.80219 12.7663 5.70344 12.7663 6.80594C12.7663 7.90844 11.865 8.80969 10.7625 8.80969C9.7825 8.80969 9.0125 7.98719 8.0325 6.95469C7.86625 6.77969 7.69125 6.59594 7.5075 6.40344C7.07 5.92219 4.8475 3.58594 3.22875 3.58594C1.44375 3.58594 0 5.03844 0 6.81469C0 8.59094 1.44375 10.0434 3.22875 10.0434C4.55 10.0434 5.7225 9.29969 6.37 8.04844C6.5275 7.75094 6.405 7.37469 6.1075 7.22594C5.81 7.06844 5.43375 7.19094 5.285 7.48844C4.8475 8.33719 4.10375 8.81844 3.2375 8.81844C2.12625 8.81844 1.225 7.91719 1.225 6.81469C1.225 5.71219 2.12625 4.81094 3.22875 4.81094C4.03375 4.81094 5.69625 6.23719 6.62375 7.24344L6.64125 7.26094C6.81625 7.44469 6.99125 7.62844 7.1575 7.80344C8.2425 8.95844 9.275 10.0434 10.78 10.0434C12.5562 10.0434 14.0088 8.59969 14.0088 6.81469C14.0088 5.02969 12.5563 3.58594 10.7713 3.58594Z" fill="var(--fill-0, white)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_0_4">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
430
activity/invite/huwaa/assets/layers/manifest.json
Normal file
@ -0,0 +1,430 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"figma": {
|
||||
"fileKey": "OTOg5cwbhVNWxkImGmzTJl",
|
||||
"canvas": {
|
||||
"id": "543:1998",
|
||||
"name": "Huwaa运营"
|
||||
},
|
||||
"entryFrames": {
|
||||
"default": "850:232",
|
||||
"win": "850:431",
|
||||
"estimatedRewards": "850:638"
|
||||
},
|
||||
"designSize": {
|
||||
"width": 375,
|
||||
"height": 1114
|
||||
},
|
||||
"forbiddenAggregateNodes": [
|
||||
"543:1998",
|
||||
"850:232",
|
||||
"850:431",
|
||||
"850:638",
|
||||
"850:237",
|
||||
"850:255",
|
||||
"850:262",
|
||||
"850:291",
|
||||
"850:314",
|
||||
"850:425",
|
||||
"850:628",
|
||||
"850:835"
|
||||
]
|
||||
},
|
||||
"assets": [
|
||||
{
|
||||
"file": "bg-palace-mask.svg",
|
||||
"sourceNode": "850:234",
|
||||
"sourceName": "image 5044",
|
||||
"sourceType": "ROUNDED_RECTANGLE",
|
||||
"purpose": "Bottom fade mask for the palace-only background layer",
|
||||
"bounds": { "width": 541, "height": 812 },
|
||||
"original": { "width": 541, "height": 812 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 542,
|
||||
"sha256": "f99c7cf545fa81ef21174ea786fa32485a2efa0bc0e0b223b04f13e29255c8cf",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "bg-palace.webp",
|
||||
"sourceNode": "850:235",
|
||||
"sourceNodes": ["850:235", "850:236"],
|
||||
"sourceName": "image 5046",
|
||||
"sourceType": "ROUNDED_RECTANGLE",
|
||||
"purpose": "Palace-only decorative background reused by both half-opacity Figma layers",
|
||||
"bounds": { "width": 541, "height": 812 },
|
||||
"original": { "width": 1024, "height": 1536 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 2406896,
|
||||
"sha256": "fb80cada233c7ebcf37aa5a0fa16c92f836db8aa7a05e7b8782426273563ddb6",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "button-awesome.webp",
|
||||
"sourceNode": "850:636",
|
||||
"sourceName": "图层 1 9",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Cropped AWESOME button skin without its DOM label",
|
||||
"bounds": { "width": 250, "height": 53 },
|
||||
"original": { "width": 250, "height": 53 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 13646,
|
||||
"sha256": "a40f8c77ed9f78921e857dc530e032997d964e396207cff931934e46f9f93888",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "button-invite.webp",
|
||||
"sourceNode": "850:426",
|
||||
"sourceName": "图层 0 315",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Invite CTA skin without its DOM label",
|
||||
"bounds": { "width": 280, "height": 50 },
|
||||
"original": { "width": 1990, "height": 360 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 417966,
|
||||
"sha256": "538d9833e1e8f8bf7bfbfbf7c6032afa18425a9fff0a661dd83d09f082b1f00b",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "button-withdraw.webp",
|
||||
"sourceNode": "850:312",
|
||||
"sourceName": "图层 1 7",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Wallet withdrawal button skin without its DOM label",
|
||||
"bounds": { "width": 214, "height": 34 },
|
||||
"original": { "width": 2055, "height": 329 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 374810,
|
||||
"sha256": "e566ebfbe19d15b667a3238df600a7cc1225d3e7ec35903443f82008760239f9",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "chest-blue.webp",
|
||||
"sourceNode": "850:267",
|
||||
"sourceName": "03 2 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Blue wheel reward chest",
|
||||
"bounds": { "width": 57, "height": 55 },
|
||||
"original": { "width": 354, "height": 336 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 149466,
|
||||
"sha256": "0551c01bc09d311d20a8a6f1323629b7bb3898d446b3b22c6e947a2c0b04b272",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "chest-gold.webp",
|
||||
"sourceNode": "850:270",
|
||||
"sourceName": "01 2 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Gold wheel reward chest",
|
||||
"bounds": { "width": 57, "height": 51 },
|
||||
"original": { "width": 355, "height": 316 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 142820,
|
||||
"sha256": "0ba668e4312b24377ead80d73b0fb87bd778dcaf8bf444a9a176e703fe88cd36",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "chest-green.webp",
|
||||
"sourceNode": "850:283",
|
||||
"sourceName": "04 2 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Green wheel reward chest",
|
||||
"bounds": { "width": 57, "height": 53 },
|
||||
"original": { "width": 354, "height": 331 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 164498,
|
||||
"sha256": "5b3e74a47979ac5f4459a4de0ceb66473d272fbb0484f9aa84807a0c630b3209",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "chest-orange.webp",
|
||||
"sourceNode": "850:279",
|
||||
"sourceName": "07 2 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Orange wheel reward chest",
|
||||
"bounds": { "width": 57, "height": 53 },
|
||||
"original": { "width": 354, "height": 325 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 178720,
|
||||
"sha256": "3e7ffe20e4309dee33cbde633597794f68a77e3b07beaff4d5e63f830dbb33e9",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "chest-purple.webp",
|
||||
"sourceNode": "850:276",
|
||||
"sourceName": "05 2 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Purple wheel reward chest",
|
||||
"bounds": { "width": 57, "height": 55 },
|
||||
"original": { "width": 354, "height": 334 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 162400,
|
||||
"sha256": "f6f782ee6440d7349fabf6955f81d71345f71a70a9d3151a20417cb40893703e",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "chest-red.webp",
|
||||
"sourceNode": "850:273",
|
||||
"sourceNodes": ["850:273", "850:429", "850:430"],
|
||||
"sourceName": "02 2 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Red wheel reward chest reused by the estimated-rewards entry",
|
||||
"bounds": { "width": 57, "height": 52 },
|
||||
"original": { "width": 353, "height": 318 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 153572,
|
||||
"sha256": "89fba58f5b4ad0aebf4fe193eabbec83070eeb6246f11f5fa41e580f150e4e39",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "chest-white-gold.webp",
|
||||
"sourceNode": "850:286",
|
||||
"sourceName": "图层 1 6",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "White-gold wheel reward chest",
|
||||
"bounds": { "width": 57, "height": 55 },
|
||||
"original": { "width": 355, "height": 341 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 174792,
|
||||
"sha256": "ed3f81ff4f21ad9a8ca3d32c42a11953b5a2e8bf9bd9ddeb1918d64a4220946b",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "icon-convert.svg",
|
||||
"sourceNode": "850:306",
|
||||
"sourceName": "转换_转换_copy 1",
|
||||
"sourceType": "INSTANCE",
|
||||
"purpose": "Points conversion arrows",
|
||||
"bounds": { "width": 12.521, "height": 14 },
|
||||
"original": { "width": 14, "height": 12.5205 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 2095,
|
||||
"sha256": "df2b2c6f97d7ae52bd61240846a9168281d2406b61bc1a39b5676061a6484ebb",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "icon-finance.svg",
|
||||
"sourceNode": "850:308",
|
||||
"sourceName": "Finance 1",
|
||||
"sourceType": "INSTANCE",
|
||||
"purpose": "Wallet finance icon",
|
||||
"bounds": { "width": 14, "height": 14 },
|
||||
"original": { "width": 14, "height": 14 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 2884,
|
||||
"sha256": "86e054e618747a8d6ec1c248fc74bc1781eeb96654cceacc4b4274db24c07be4",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "icon-infinity.svg",
|
||||
"sourceNode": "850:304",
|
||||
"sourceName": "符号_无限 1",
|
||||
"sourceType": "INSTANCE",
|
||||
"purpose": "Infinite points indicator",
|
||||
"bounds": { "width": 14, "height": 14 },
|
||||
"original": { "width": 14, "height": 14 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1472,
|
||||
"sha256": "58b7569f3a7f018c492bcfb1633d58c73b4fc807cb9e4a86ff5a03845dd2f841",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "nav-back.svg",
|
||||
"sourceNode": "850:257",
|
||||
"sourceName": "icon_20_返回",
|
||||
"sourceType": "INSTANCE",
|
||||
"purpose": "Navigation back icon",
|
||||
"bounds": { "width": 24, "height": 24 },
|
||||
"original": { "width": 24, "height": 24 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 419,
|
||||
"sha256": "50d06f24adb0019fcdfb3c3f5db012808c230cd8c2f06c61b775fcccdbf525d4",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "panel-gold-emerald.webp",
|
||||
"sourceNode": "850:292",
|
||||
"sourceNodes": ["850:292", "850:315", "850:836"],
|
||||
"sourceName": "图层 0 46",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Reusable gold-and-emerald panel skin reconstructed without Figma one-pixel slices",
|
||||
"bounds": { "width": 351, "height": 205 },
|
||||
"original": { "width": 1502, "height": 879 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 722510,
|
||||
"sha256": "6780e103ca19992a5fa3b1e6be5f0d598dab26d803fb9463f9e01fc2fce49789",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "points-a.webp",
|
||||
"sourceNode": "850:297",
|
||||
"sourceNodes": ["850:297", "850:302"],
|
||||
"sourceName": "图层 0 47",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Huwaa A points icon",
|
||||
"bounds": { "width": 23, "height": 24 },
|
||||
"original": { "width": 868, "height": 892 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 514910,
|
||||
"sha256": "5e3b941cc0f6224ccb17017fad201a508d96230ff70d3f1268d2765636eb2240",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "reward-calendar-check.webp",
|
||||
"sourceNode": "850:394",
|
||||
"sourceNodes": ["850:394", "850:405", "850:416", "850:1085"],
|
||||
"sourceName": "image 5049",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Task icon and reward-list fallback icon",
|
||||
"bounds": { "width": 42, "height": 45 },
|
||||
"original": { "width": 658, "height": 701 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 182252,
|
||||
"sha256": "79b06b912c2e64ff3487bf8d35b848f493df4134583ba16c34d35ce0427ba81b",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "reward-modal-panel.webp",
|
||||
"sourceNode": "850:629",
|
||||
"sourceName": "图层 0 316",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Win dialog decorative panel without reward media or text",
|
||||
"bounds": { "width": 327, "height": 415 },
|
||||
"original": { "width": 994, "height": 1262 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 960078,
|
||||
"sha256": "26ded67cb3b3f917d68e517627a3328dbb02c223f3590f2ac3ad9609e510627f",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "status-battery.svg",
|
||||
"sourceNode": "850:240",
|
||||
"sourceName": "Battery",
|
||||
"sourceType": "INSTANCE",
|
||||
"purpose": "Design status-bar battery icon",
|
||||
"bounds": { "width": 24.33, "height": 11.3333 },
|
||||
"original": { "width": 24.33, "height": 11.3333 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1005,
|
||||
"sha256": "62fd408926ad465cc16e1fb57634dc5ff2205b4ab11a6b0edc110440e83f0f2b",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "status-cellular.svg",
|
||||
"sourceNode": "850:248",
|
||||
"sourceName": "Cellular Connection",
|
||||
"sourceType": "INSTANCE",
|
||||
"purpose": "Design status-bar cellular icon",
|
||||
"bounds": { "width": 17, "height": 11.3334 },
|
||||
"original": { "width": 17.0001, "height": 11.3334 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1147,
|
||||
"sha256": "112f067ba1b97345f39df94cba9e080a46606a4387fe913344a2b2e7e7aee13e",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "status-wifi.svg",
|
||||
"sourceNode": "850:244",
|
||||
"sourceName": "Wifi",
|
||||
"sourceType": "INSTANCE",
|
||||
"purpose": "Design status-bar Wi-Fi icon",
|
||||
"bounds": { "width": 15.3333, "height": 11.01 },
|
||||
"original": { "width": 15.3333, "height": 11.01 },
|
||||
"exportFormat": "svg",
|
||||
"bytes": 1633,
|
||||
"sha256": "22cf5fae5ae340219fbe1ed699d3a7d1562f6f4993c24df5502c640f907a1ec5",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "wallet-inner-panel.webp",
|
||||
"sourceNode": "850:293",
|
||||
"sourceName": "成v下 1",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Wallet inner texture behind live balance values",
|
||||
"bounds": { "width": 327, "height": 120 },
|
||||
"original": { "width": 1464, "height": 622 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 459290,
|
||||
"sha256": "e8fe298ba89673bb8058668481dee7023b30e4445f83ae3363d8202796750f1c",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "wheel-base.webp",
|
||||
"sourceNode": "850:264",
|
||||
"sourceName": "图层 0 49",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Empty wheel frame, stage, and pedestal without prizes or controls",
|
||||
"bounds": { "width": 370, "height": 437.464 },
|
||||
"original": { "width": 1038, "height": 1227 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 1118800,
|
||||
"sha256": "abb8c248b21d09716eb7022aa991fd18c0452ec7a2f832792104dbeb533b58bb",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
},
|
||||
{
|
||||
"file": "wheel-spin-center.webp",
|
||||
"sourceNode": "850:265",
|
||||
"sourceName": "图层 0 43",
|
||||
"sourceType": "RECTANGLE",
|
||||
"purpose": "Wheel center and pointer skin without Spin or cost text",
|
||||
"bounds": { "width": 106, "height": 117 },
|
||||
"original": { "width": 756, "height": 840 },
|
||||
"exportFormat": "webp-lossless",
|
||||
"bytes": 407758,
|
||||
"sha256": "09659e624c1cc7979ca847142f00e5c322653ad531f7752878d7fe9cc90c6506",
|
||||
"containsText": false,
|
||||
"containsControls": false,
|
||||
"containsDynamicMedia": false
|
||||
}
|
||||
]
|
||||
}
|
||||
9
activity/invite/huwaa/assets/layers/nav-back.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon_20_返回">
|
||||
<g id="Vector">
|
||||
</g>
|
||||
<g id="Vector_2">
|
||||
</g>
|
||||
<path id="Vector_3" d="M16 4L8 12L16 20" stroke="var(--stroke-0, white)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 419 B |
BIN
activity/invite/huwaa/assets/layers/panel-gold-emerald.webp
Normal file
|
After Width: | Height: | Size: 706 KiB |
BIN
activity/invite/huwaa/assets/layers/points-a.webp
Normal file
|
After Width: | Height: | Size: 503 KiB |
BIN
activity/invite/huwaa/assets/layers/reward-calendar-check.webp
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
activity/invite/huwaa/assets/layers/reward-modal-panel.webp
Normal file
|
After Width: | Height: | Size: 938 KiB |
7
activity/invite/huwaa/assets/layers/status-battery.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 24.33 11.3333" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Battery">
|
||||
<path id="Border" opacity="0.35" d="M2.66699 0.5H19.333C20.5296 0.5 21.5 1.47038 21.5 2.66699V8.66699C21.4998 9.86346 20.5295 10.833 19.333 10.833H2.66699C1.4705 10.833 0.500175 9.86346 0.5 8.66699V2.66699C0.5 1.47038 1.47039 0.5 2.66699 0.5Z" fill="var(--fill-0, white)" stroke="var(--stroke-0, white)"/>
|
||||
<path id="Cap" opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M23.002 7.66406C23.8067 7.32529 24.33 6.53719 24.33 5.66406C24.33 4.79093 23.8067 4.00284 23.002 3.66406V7.66406V7.66406Z" fill="var(--fill-0, white)"/>
|
||||
<path id="Capacity" fill-rule="evenodd" clip-rule="evenodd" d="M18.6667 2C19.403 2 20 2.59696 20 3.33333V8C20 8.73638 19.403 9.33333 18.6667 9.33333H3.33333C2.59695 9.33333 2 8.73638 2 8V3.33333C2 2.59696 2.59695 2 3.33333 2H18.6667V2Z" fill="var(--fill-0, white)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1005 B |
3
activity/invite/huwaa/assets/layers/status-cellular.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 17.0001 11.3334" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Cellular Connection" fill-rule="evenodd" clip-rule="evenodd" d="M11.3301 3.00002C11.8824 3.00002 12.3301 3.44774 12.3301 4.00002V10.3334C12.3301 10.8856 11.8824 11.3334 11.3301 11.3334H10.3301C9.77779 11.3334 9.33008 10.8856 9.33008 10.3334V4.00002C9.33008 3.44774 9.77779 3.00002 10.3301 3.00002H11.3301V3.00002ZM16.0001 0C16.5524 0 17.0001 0.447712 17.0001 1V9.66667C17.0001 10.219 16.5524 10.6667 16.0001 10.6667H15.0001C14.4479 10.6667 14.0001 10.219 14.0001 9.66667V1C14.0001 0.447712 14.4479 0 15.0001 0V0H16.0001V0ZM2 7.00002C2.55229 7.00002 3 7.44774 3 8.00002V10C3 10.5523 2.55229 11 2 11H0.999999C0.447714 11 0 10.5523 0 10V8.00002C0 7.44774 0.447714 7.00002 0.999999 7.00002H2V7.00002ZM6.66211 5.00002C7.2144 5.00002 7.66211 5.44774 7.66211 6.00003V10C7.66211 10.5523 7.2144 11 6.66211 11H5.66211C5.10982 11 4.66211 10.5523 4.66211 10V6.00003C4.66211 5.44774 5.10982 5.00002 5.66211 5.00002H6.66211V5.00002Z" fill="var(--fill-0, white)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
activity/invite/huwaa/assets/layers/status-wifi.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 15.3333 11.01" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Wifi" fill-rule="evenodd" clip-rule="evenodd" d="M9.99235 8.6639C9.99057 8.57712 9.95304 8.49492 9.88861 8.4367C8.60795 7.35443 6.7323 7.35443 5.45164 8.4367C5.38717 8.49487 5.34957 8.57705 5.34773 8.66383C5.3459 8.75061 5.37998 8.8343 5.44194 8.89514L7.44757 10.9171C7.50636 10.9766 7.5865 11.01 7.67013 11.01C7.75375 11.01 7.83389 10.9766 7.89267 10.9171L9.89797 8.89514C9.95998 8.83434 9.99412 8.75068 9.99235 8.6639V8.6639ZM13.6438 4.67028C13.7652 4.78852 13.9593 4.78703 14.0789 4.66693L15.2395 3.4966C15.3 3.43569 15.3338 3.35318 15.3333 3.26733C15.3328 3.18148 15.2981 3.09937 15.2368 3.03917C11.0049 -1.01306 4.32858 -1.01306 0.0966678 3.03917C0.0353653 3.09933 0.00057401 3.18141 6.67831e-06 3.26726C-0.000545321 3.35311 0.0331419 3.43564 0.0936471 3.4966L1.25459 4.66693C1.3741 4.78721 1.56831 4.7887 1.68966 4.67028C3.30222 3.13859 5.44213 2.28447 7.66707 2.28448C9.89181 2.28457 12.0315 3.13869 13.6438 4.67028V4.67028ZM10.978 7.36445C11.1006 7.48079 11.2936 7.47827 11.4131 7.35876L12.5724 6.18843C12.6334 6.12705 12.6673 6.04377 12.6664 5.95724C12.6655 5.8707 12.63 5.78813 12.5677 5.728C9.80853 3.16359 5.53562 3.16359 2.77646 5.728C2.71413 5.78813 2.67857 5.87074 2.67775 5.95731C2.67694 6.04387 2.71093 6.12714 2.7721 6.18843L3.93104 7.35876C4.05051 7.47827 4.24356 7.48079 4.36611 7.36445C5.27158 6.54522 6.44936 6.0913 7.6709 6.0908C8.89325 6.09072 10.072 6.54468 10.978 7.36445V7.36445Z" fill="var(--fill-0, white)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
activity/invite/huwaa/assets/layers/wallet-inner-panel.webp
Normal file
|
After Width: | Height: | Size: 448 KiB |
BIN
activity/invite/huwaa/assets/layers/wheel-base.webp
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
activity/invite/huwaa/assets/layers/wheel-spin-center.webp
Normal file
|
After Width: | Height: | Size: 398 KiB |
1013
activity/invite/huwaa/huwaa.css
Normal file
1009
activity/invite/huwaa/huwaa.js
Normal file
171
activity/recharge-reward/fami.html
Normal file
@ -0,0 +1,171 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-hy-app-code="fami">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<title>Recharge Reward</title>
|
||||
<link rel="stylesheet" href="../../common/theme.css?v=20260716" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="./fami/fami.css?v=20260716-figma-822-2928"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="image"
|
||||
href="./fami/assets/background-source-844-7068.png"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="fami-recharge-app" id="appViewport">
|
||||
<main class="stage-wrap" id="stageWrap">
|
||||
<section
|
||||
class="stage"
|
||||
id="stage"
|
||||
data-node-id="822:2928"
|
||||
aria-label="Recharge Reward activity"
|
||||
data-i18n-aria="rechargeReward.pageLabel"
|
||||
aria-busy="true"
|
||||
>
|
||||
<div class="activity-background" aria-hidden="true">
|
||||
<div
|
||||
class="background-segment background-segment-mid"
|
||||
data-node-id="844:7094"
|
||||
>
|
||||
<img
|
||||
src="./fami/assets/background-source-844-7068.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="background-segment background-segment-top"
|
||||
data-node-id="822:2929"
|
||||
>
|
||||
<img
|
||||
src="./fami/assets/background-source-844-7068.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="background-segment background-segment-bottom"
|
||||
data-node-id="844:7090"
|
||||
>
|
||||
<img
|
||||
src="./fami/assets/background-source-844-7068.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section
|
||||
class="countdown"
|
||||
id="countdown"
|
||||
data-node-id="822:2953"
|
||||
aria-label="Activity countdown"
|
||||
>
|
||||
<img
|
||||
class="countdown-frame"
|
||||
src="./fami/assets/countdown-frame-822-2954.png"
|
||||
alt=""
|
||||
data-node-id="822:2954"
|
||||
/>
|
||||
<output
|
||||
class="count-value count-value-days"
|
||||
data-count="days"
|
||||
></output>
|
||||
<output
|
||||
class="count-value count-value-hours"
|
||||
data-count="hours"
|
||||
></output>
|
||||
<output
|
||||
class="count-value count-value-minutes"
|
||||
data-count="minutes"
|
||||
></output>
|
||||
<output
|
||||
class="count-value count-value-seconds"
|
||||
data-count="seconds"
|
||||
></output>
|
||||
<span
|
||||
class="count-label count-label-days"
|
||||
data-node-id="822:2955"
|
||||
data-i18n="rechargeReward.days"
|
||||
>Days</span
|
||||
>
|
||||
<span
|
||||
class="count-label count-label-hours"
|
||||
data-node-id="822:2956"
|
||||
data-i18n="rechargeReward.hours"
|
||||
>Hours</span
|
||||
>
|
||||
<span
|
||||
class="count-label count-label-minutes"
|
||||
data-node-id="822:2957"
|
||||
data-i18n="rechargeReward.minutes"
|
||||
>Minutes</span
|
||||
>
|
||||
<span
|
||||
class="count-label count-label-seconds"
|
||||
data-node-id="822:2958"
|
||||
data-i18n="rechargeReward.seconds"
|
||||
>Seconds</span
|
||||
>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="profile-card"
|
||||
data-node-id="822:2959"
|
||||
aria-label="My recharge status"
|
||||
>
|
||||
<img
|
||||
class="profile-frame"
|
||||
src="./fami/assets/profile-frame-840-4737.png"
|
||||
alt=""
|
||||
data-node-id="840:4737"
|
||||
/>
|
||||
<img
|
||||
class="profile-avatar"
|
||||
id="profileAvatar"
|
||||
alt=""
|
||||
data-node-id="822:2960"
|
||||
hidden
|
||||
/>
|
||||
<h1
|
||||
class="profile-name"
|
||||
id="profileName"
|
||||
data-node-id="822:2962"
|
||||
></h1>
|
||||
<p
|
||||
class="profile-id"
|
||||
id="profileID"
|
||||
data-node-id="822:2964"
|
||||
></p>
|
||||
<p
|
||||
class="profile-recharge"
|
||||
id="profileRecharge"
|
||||
data-node-id="822:2963"
|
||||
></p>
|
||||
</section>
|
||||
|
||||
<div class="reward-sections" id="rewardSections"></div>
|
||||
<p
|
||||
class="page-status"
|
||||
id="pageStatus"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
hidden
|
||||
></p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Fami 页面即使由旧客户端漏传 app_code,也必须稳定请求 Fami 租户;显式 query 仍由 common/api.js 优先处理。
|
||||
window.HyAppDefaultAppCode = 'fami';
|
||||
</script>
|
||||
<script src="../../common/i18n.js?v=20260716"></script>
|
||||
<script src="../../common/api.js?v=20260716"></script>
|
||||
<script src="./fami/fami.js?v=20260716-figma-822-2928"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 479 KiB |
BIN
activity/recharge-reward/fami/assets/mock-avatar-822-2960.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
activity/recharge-reward/fami/assets/mock-reward-822-2971.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
activity/recharge-reward/fami/assets/profile-frame-840-4737.png
Normal file
|
After Width: | Height: | Size: 903 KiB |
BIN
activity/recharge-reward/fami/assets/receive-skin-822-2990.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
activity/recharge-reward/fami/assets/reward-card-822-2969.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
activity/recharge-reward/fami/assets/reward-panel-822-2967.png
Normal file
|
After Width: | Height: | Size: 692 KiB |
437
activity/recharge-reward/fami/fami.css
Normal file
@ -0,0 +1,437 @@
|
||||
@font-face {
|
||||
font-family: 'Fami Source Han Sans SC';
|
||||
src: url('./assets/fonts/source-han-sans-sc-regular-latin.woff2')
|
||||
format('woff2');
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fami Source Han Sans SC';
|
||||
src: url('./assets/fonts/source-han-sans-sc-medium-latin.woff2')
|
||||
format('woff2');
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
:root {
|
||||
--fami-design-width: 1080;
|
||||
--fami-stage-scale: 0.3472222222;
|
||||
--fami-stage-height: 3273px;
|
||||
--fami-background-bottom-top: 2695px;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
overflow-x: hidden;
|
||||
background: #080116;
|
||||
color: #f6e0b2;
|
||||
font-family: 'Fami Source Han Sans SC', Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fami-recharge-app {
|
||||
width: min(100vw, 430px);
|
||||
min-height: calc(3273px * var(--fami-stage-scale));
|
||||
overflow: hidden;
|
||||
background: #080116;
|
||||
}
|
||||
|
||||
.stage-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(3273px * var(--fami-stage-scale));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stage {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1080px;
|
||||
height: var(--fami-stage-height);
|
||||
overflow: hidden;
|
||||
transform: scale(var(--fami-stage-scale));
|
||||
transform-origin: top left;
|
||||
background: #080116;
|
||||
}
|
||||
|
||||
.activity-background,
|
||||
.reward-sections {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.activity-background {
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.background-segment {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.background-segment img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
height: 1887px;
|
||||
max-width: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Figma 用同一背景节点的三段蒙版拼接长页;这里保留原始裁切坐标,而不是把根 Frame 当整页图片。 */
|
||||
.background-segment-mid {
|
||||
top: 1311px;
|
||||
height: 405px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.background-segment-mid img {
|
||||
top: -1168px;
|
||||
}
|
||||
|
||||
.background-segment-top {
|
||||
top: 0;
|
||||
height: 1588px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.background-segment-top img {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.background-segment-bottom {
|
||||
top: var(--fami-background-bottom-top);
|
||||
height: 578px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.background-segment-bottom img {
|
||||
top: -1333px;
|
||||
}
|
||||
|
||||
.countdown,
|
||||
.profile-card,
|
||||
.reward-sections,
|
||||
.page-status {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
left: 90px;
|
||||
top: 696px;
|
||||
width: 900px;
|
||||
height: 393px;
|
||||
}
|
||||
|
||||
.countdown-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 900px;
|
||||
height: 393px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.count-label,
|
||||
.count-value {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.count-label {
|
||||
top: 280px;
|
||||
width: 180px;
|
||||
height: 24px;
|
||||
color: #050300;
|
||||
font-family: 'Fami Source Han Sans SC', Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.count-value {
|
||||
display: none;
|
||||
top: 142px;
|
||||
width: 150px;
|
||||
height: 92px;
|
||||
color: #f6e0b2;
|
||||
font-size: 68px;
|
||||
font-weight: 500;
|
||||
line-height: 92px;
|
||||
text-shadow: 0 3px 8px rgba(0, 0, 0, 0.72);
|
||||
}
|
||||
|
||||
.countdown.has-values .count-value {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.count-label-days,
|
||||
.count-value-days {
|
||||
left: 145px;
|
||||
}
|
||||
|
||||
.count-label-hours,
|
||||
.count-value-hours {
|
||||
left: 349px;
|
||||
}
|
||||
|
||||
.count-label-minutes,
|
||||
.count-value-minutes {
|
||||
left: 553.5px;
|
||||
}
|
||||
|
||||
.count-label-seconds,
|
||||
.count-value-seconds {
|
||||
left: 753.5px;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
left: 90px;
|
||||
top: 1089px;
|
||||
width: 900px;
|
||||
height: 679px;
|
||||
}
|
||||
|
||||
.profile-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 900px;
|
||||
height: 679px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
position: absolute;
|
||||
left: 309px;
|
||||
top: 134px;
|
||||
width: 282px;
|
||||
height: 270px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.profile-name,
|
||||
.profile-id,
|
||||
.profile-recharge {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
width: 700px;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
color: #f6e0b2;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
top: 425px;
|
||||
height: 36px;
|
||||
font-family: 'Fami Source Han Sans SC', Arial, sans-serif;
|
||||
font-size: 36px;
|
||||
font-weight: 400;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.profile-id {
|
||||
top: 476px;
|
||||
height: 36px;
|
||||
font-family: 'Fami Source Han Sans SC', Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.profile-recharge {
|
||||
top: 520px;
|
||||
height: 40px;
|
||||
font-size: 36px;
|
||||
font-weight: 500;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.reward-sections {
|
||||
width: 1080px;
|
||||
height: var(--fami-stage-height);
|
||||
}
|
||||
|
||||
.reward-section {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: var(--section-top);
|
||||
width: 1080px;
|
||||
height: 720px;
|
||||
}
|
||||
|
||||
.reward-panel {
|
||||
position: absolute;
|
||||
left: 90px;
|
||||
top: 0;
|
||||
width: 900px;
|
||||
height: 689px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.reward-title {
|
||||
position: absolute;
|
||||
left: 529.5px;
|
||||
top: 95px;
|
||||
margin: 0;
|
||||
transform: translateX(-50%);
|
||||
color: #f6e0b2;
|
||||
font-size: 40px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.reward-section.is-following .reward-title {
|
||||
left: 559.5px;
|
||||
top: 99px;
|
||||
}
|
||||
|
||||
.reward-card {
|
||||
position: absolute;
|
||||
width: 229px;
|
||||
height: 258px;
|
||||
color: #f6e0b2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reward-card-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 229px;
|
||||
height: 258px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.reward-media {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
top: 19px;
|
||||
width: 193px;
|
||||
height: 193px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.reward-section.is-following .reward-media {
|
||||
left: 18.5px;
|
||||
top: 21px;
|
||||
}
|
||||
|
||||
.reward-price {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 202px;
|
||||
width: 229px;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
color: #f6e0b2;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.reward-action {
|
||||
position: absolute;
|
||||
left: 341px;
|
||||
top: 591px;
|
||||
width: 399px;
|
||||
height: 129px;
|
||||
}
|
||||
|
||||
.reward-section.is-following .reward-action {
|
||||
top: 604px;
|
||||
}
|
||||
|
||||
.reward-action-skin {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 399px;
|
||||
height: 129px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.reward-action-label {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 45px;
|
||||
width: 399px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
color: #f6e0b2;
|
||||
font-family:
|
||||
'SF Pro Display',
|
||||
'SF Pro Text',
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 40px;
|
||||
font-weight: 510;
|
||||
font-variation-settings:
|
||||
'wght' 510,
|
||||
'wdth' 100;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.page-status {
|
||||
left: 190px;
|
||||
top: 1730px;
|
||||
width: 700px;
|
||||
margin: 0;
|
||||
color: #f6e0b2;
|
||||
font-size: 28px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 431px) {
|
||||
html,
|
||||
body {
|
||||
background: #05000e;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
495
activity/recharge-reward/fami/fami.js
Normal file
@ -0,0 +1,495 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var DESIGN_WIDTH = 1080;
|
||||
var BASE_STAGE_HEIGHT = 3273;
|
||||
var SECTION_STEP = 720;
|
||||
var MAX_VIEWPORT_WIDTH = 430;
|
||||
var STATUS_PATH = '/api/v1/activities/cumulative-recharge-reward';
|
||||
|
||||
var appViewport = document.getElementById('appViewport');
|
||||
var stageWrap = document.getElementById('stageWrap');
|
||||
var stage = document.getElementById('stage');
|
||||
var countdown = document.getElementById('countdown');
|
||||
var rewardSections = document.getElementById('rewardSections');
|
||||
var profileName = document.getElementById('profileName');
|
||||
var profileID = document.getElementById('profileID');
|
||||
var profileRecharge = document.getElementById('profileRecharge');
|
||||
var profileAvatar = document.getElementById('profileAvatar');
|
||||
var pageStatus = document.getElementById('pageStatus');
|
||||
var countNodes = {
|
||||
days: document.querySelector('[data-count="days"]'),
|
||||
hours: document.querySelector('[data-count="hours"]'),
|
||||
minutes: document.querySelector('[data-count="minutes"]'),
|
||||
seconds: document.querySelector('[data-count="seconds"]'),
|
||||
};
|
||||
|
||||
var currentData = null;
|
||||
var designHeight = BASE_STAGE_HEIGHT;
|
||||
var countdownEndAt = 0;
|
||||
var timeOffsetMS = 0;
|
||||
var countdownTimer = 0;
|
||||
|
||||
function queryParams() {
|
||||
return new URLSearchParams(window.location.search || '');
|
||||
}
|
||||
|
||||
function shouldUseMock(params) {
|
||||
var value = String(params.get('mock') || '').toLowerCase();
|
||||
// 设计数据只允许显式 mock=1/true;生产接口失败时不得回退到看似可用的虚假奖励。
|
||||
return value === '1' || value === 'true';
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
return window.HyAppI18n && typeof window.HyAppI18n.t === 'function'
|
||||
? window.HyAppI18n.t(key, fallback)
|
||||
: fallback || key;
|
||||
}
|
||||
|
||||
function escapeHTML(value) {
|
||||
return String(value == null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function firstValue(source, keys, fallback) {
|
||||
source = source && typeof source === 'object' ? source : {};
|
||||
for (var index = 0; index < keys.length; index += 1) {
|
||||
var value = source[keys[index]];
|
||||
if (value !== undefined && value !== null && value !== '')
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function numberValue(source, keys, fallback) {
|
||||
var value = Number(firstValue(source, keys, fallback));
|
||||
return Number.isFinite(value) ? value : Number(fallback || 0);
|
||||
}
|
||||
|
||||
function wholeUSD(minor) {
|
||||
var value = Math.max(0, Number(minor || 0)) / 100;
|
||||
return value % 1 === 0
|
||||
? String(value)
|
||||
: value.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
|
||||
}
|
||||
|
||||
function tierAmount(minor) {
|
||||
return '$' + wholeUSD(minor);
|
||||
}
|
||||
|
||||
function twoDigits(value) {
|
||||
return String(Math.max(0, Math.floor(value))).padStart(2, '0');
|
||||
}
|
||||
|
||||
function updateScale() {
|
||||
var viewportWidth = Math.min(
|
||||
window.innerWidth || 375,
|
||||
MAX_VIEWPORT_WIDTH
|
||||
);
|
||||
var scale = viewportWidth / DESIGN_WIDTH;
|
||||
var renderedHeight = designHeight * scale;
|
||||
document.documentElement.style.setProperty(
|
||||
'--fami-stage-scale',
|
||||
String(scale)
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
'--fami-stage-height',
|
||||
designHeight + 'px'
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
'--fami-background-bottom-top',
|
||||
designHeight - 578 + 'px'
|
||||
);
|
||||
appViewport.style.minHeight = renderedHeight + 'px';
|
||||
stageWrap.style.height = renderedHeight + 'px';
|
||||
stage.style.height = designHeight + 'px';
|
||||
}
|
||||
|
||||
function mockData(params) {
|
||||
var rechargeValue = Number(params.get('recharge') || 5998);
|
||||
var explicitEnd = Date.parse(
|
||||
params.get('end_at') || params.get('endAt') || ''
|
||||
);
|
||||
var rewardItem = {
|
||||
item_type: 'resource',
|
||||
resource: {
|
||||
name: 'Figma reward fixture',
|
||||
preview_url: './fami/assets/mock-reward-822-2971.png',
|
||||
},
|
||||
};
|
||||
return {
|
||||
enabled: true,
|
||||
user: {
|
||||
username: params.get('name') || 'nanfangjian',
|
||||
display_user_id:
|
||||
params.get('uid') || params.get('id') || '12345678',
|
||||
avatar:
|
||||
params.get('avatar') ||
|
||||
'./fami/assets/mock-avatar-822-2960.png',
|
||||
},
|
||||
period: {
|
||||
// Figma 节点没有倒计时数字;只有显式 end_at 才展示动态数字,避免凭空猜一个验收值。
|
||||
end_ms: Number.isFinite(explicitEnd) ? explicitEnd : 0,
|
||||
},
|
||||
progress: {
|
||||
total_usd_minor: Math.max(0, Math.round(rechargeValue * 100)),
|
||||
},
|
||||
tiers: [
|
||||
{
|
||||
tier_id: 1,
|
||||
threshold_usd_minor: 10000,
|
||||
reached: true,
|
||||
resource_group: { items: [rewardItem, rewardItem] },
|
||||
},
|
||||
{
|
||||
tier_id: 2,
|
||||
threshold_usd_minor: 10000,
|
||||
reached: true,
|
||||
resource_group: {
|
||||
items: [rewardItem, rewardItem, rewardItem],
|
||||
},
|
||||
},
|
||||
],
|
||||
grants: [],
|
||||
server_time_ms: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
function resourcePreview(item) {
|
||||
var resource = (item && item.resource) || {};
|
||||
// preview_url 是 gateway 明确下发给静态 H5 的封面;asset_url 可能是动画,仅作为旧数据兜底。
|
||||
return String(
|
||||
firstValue(
|
||||
resource,
|
||||
[
|
||||
'preview_url',
|
||||
'previewUrl',
|
||||
'asset_url',
|
||||
'assetUrl',
|
||||
'animation_url',
|
||||
'animationUrl',
|
||||
],
|
||||
''
|
||||
) || ''
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeData(payload) {
|
||||
payload = payload && payload.status ? payload.status : payload || {};
|
||||
var config = payload.config || {};
|
||||
var rawTiers = Array.isArray(payload.tiers)
|
||||
? payload.tiers
|
||||
: Array.isArray(config.tiers)
|
||||
? config.tiers
|
||||
: [];
|
||||
var grants = Array.isArray(payload.grants) ? payload.grants : [];
|
||||
var grantByTier = {};
|
||||
grants.forEach(function (grant) {
|
||||
var tierID = String(firstValue(grant, ['tier_id', 'tierId'], ''));
|
||||
if (tierID) grantByTier[tierID] = grant;
|
||||
});
|
||||
|
||||
var tiers = rawTiers
|
||||
.slice()
|
||||
.sort(function (a, b) {
|
||||
return (
|
||||
numberValue(a, ['sort_order', 'sortOrder'], 0) -
|
||||
numberValue(b, ['sort_order', 'sortOrder'], 0)
|
||||
);
|
||||
})
|
||||
.map(function (tier) {
|
||||
var group = tier.resource_group || tier.resourceGroup || {};
|
||||
var items = Array.isArray(group.items) ? group.items : [];
|
||||
var tierID = String(
|
||||
firstValue(tier, ['tier_id', 'tierId'], '')
|
||||
);
|
||||
var grant = tier.grant || grantByTier[tierID] || null;
|
||||
return {
|
||||
tierID: tierID,
|
||||
thresholdUSDMinor: numberValue(
|
||||
tier,
|
||||
['threshold_usd_minor', 'thresholdUsdMinor'],
|
||||
0
|
||||
),
|
||||
reached: Boolean(firstValue(tier, ['reached'], false)),
|
||||
grant: grant,
|
||||
cards: items.slice(0, 3).map(function (item) {
|
||||
return {
|
||||
name: firstValue(
|
||||
(item && item.resource) || {},
|
||||
['name'],
|
||||
''
|
||||
),
|
||||
preview: resourcePreview(item),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
enabled:
|
||||
firstValue(
|
||||
payload,
|
||||
['enabled'],
|
||||
firstValue(config, ['enabled'], true)
|
||||
) !== false,
|
||||
user: payload.user || {},
|
||||
period: payload.period || {},
|
||||
progress: payload.progress || {},
|
||||
serverTimeMS: numberValue(
|
||||
payload,
|
||||
['server_time_ms', 'serverTimeMs'],
|
||||
Date.now()
|
||||
),
|
||||
tiers: tiers,
|
||||
};
|
||||
}
|
||||
|
||||
function grantWasIssued(grant) {
|
||||
if (!grant) return false;
|
||||
var status = String(firstValue(grant, ['status'], '')).toLowerCase();
|
||||
return (
|
||||
['granted', 'success', 'succeeded', 'completed'].indexOf(status) >=
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
function cardPositions(count) {
|
||||
if (count <= 1) return [425.5];
|
||||
if (count === 2) return [201, 650];
|
||||
return [160.5, 429.5, 698.5];
|
||||
}
|
||||
|
||||
function renderCard(card, section, cardIndex, left) {
|
||||
var media = card.preview
|
||||
? '<img class="reward-media" src="' +
|
||||
escapeHTML(card.preview) +
|
||||
'" alt="' +
|
||||
escapeHTML(card.name) +
|
||||
'" />'
|
||||
: '';
|
||||
return [
|
||||
'<article class="reward-card" style="left:',
|
||||
left,
|
||||
'px;top:',
|
||||
section.cardTop,
|
||||
'px" data-card-index="',
|
||||
cardIndex,
|
||||
'">',
|
||||
'<img class="reward-card-frame" src="./fami/assets/reward-card-822-2969.png" alt="" data-node-id="822:2969" />',
|
||||
media,
|
||||
'<span class="reward-price">',
|
||||
escapeHTML(tierAmount(section.thresholdUSDMinor)),
|
||||
'</span>',
|
||||
'</article>',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function renderSections(tiers) {
|
||||
designHeight = Math.max(
|
||||
BASE_STAGE_HEIGHT,
|
||||
BASE_STAGE_HEIGHT + Math.max(0, tiers.length - 2) * SECTION_STEP
|
||||
);
|
||||
rewardSections.innerHTML = tiers
|
||||
.map(function (tier, sectionIndex) {
|
||||
var section = {
|
||||
thresholdUSDMinor: tier.thresholdUSDMinor,
|
||||
cardTop: sectionIndex === 0 ? 293 : 278,
|
||||
};
|
||||
var positions = cardPositions(tier.cards.length);
|
||||
var actionKey = grantWasIssued(tier.grant)
|
||||
? 'rechargeReward.claimed'
|
||||
: 'rechargeReward.receive';
|
||||
var actionFallback = grantWasIssued(tier.grant)
|
||||
? 'Claimed'
|
||||
: 'Receive';
|
||||
return [
|
||||
'<section class="reward-section',
|
||||
sectionIndex > 0 ? ' is-following' : '',
|
||||
'" style="--section-top:',
|
||||
1776 + sectionIndex * SECTION_STEP,
|
||||
'px" data-tier-id="',
|
||||
escapeHTML(tier.tierID),
|
||||
'" data-node-id="',
|
||||
sectionIndex === 0 ? '822:2965' : '822:2976',
|
||||
'">',
|
||||
'<img class="reward-panel" src="./fami/assets/reward-panel-822-2967.png" alt="" data-node-id="822:2967" />',
|
||||
'<h2 class="reward-title">',
|
||||
escapeHTML(
|
||||
t(
|
||||
'rechargeReward.rechargeTitle',
|
||||
'Recharge {amount}'
|
||||
).replace(
|
||||
'{amount}',
|
||||
tierAmount(tier.thresholdUSDMinor)
|
||||
)
|
||||
),
|
||||
'</h2>',
|
||||
tier.cards
|
||||
.map(function (card, cardIndex) {
|
||||
return renderCard(
|
||||
card,
|
||||
section,
|
||||
cardIndex,
|
||||
positions[cardIndex]
|
||||
);
|
||||
})
|
||||
.join(''),
|
||||
// 累充奖励由 wallet 充值事件自动发放,gateway 没有 claim RPC;设计中的 Receive 仅显示服务端状态,禁止伪造领取请求。
|
||||
'<div class="reward-action" role="status" aria-label="',
|
||||
escapeHTML(t(actionKey, actionFallback)),
|
||||
'" data-node-id="',
|
||||
sectionIndex === 0 ? '822:2991' : '822:2990',
|
||||
'">',
|
||||
'<img class="reward-action-skin" src="./fami/assets/receive-skin-822-2990.png" alt="" />',
|
||||
'<span class="reward-action-label">',
|
||||
escapeHTML(t(actionKey, actionFallback)),
|
||||
'</span>',
|
||||
'</div>',
|
||||
'</section>',
|
||||
].join('');
|
||||
})
|
||||
.join('');
|
||||
updateScale();
|
||||
}
|
||||
|
||||
function renderProfile(data) {
|
||||
var user = data.user || {};
|
||||
var displayID = firstValue(
|
||||
user,
|
||||
['display_user_id', 'displayUserId', 'user_id', 'userId'],
|
||||
''
|
||||
);
|
||||
var name = firstValue(user, ['username', 'name'], '');
|
||||
var avatar = String(
|
||||
firstValue(user, ['avatar', 'avatar_url', 'avatarUrl'], '') || ''
|
||||
);
|
||||
var totalMinor = numberValue(
|
||||
data.progress || {},
|
||||
['total_usd_minor', 'totalUsdMinor'],
|
||||
0
|
||||
);
|
||||
|
||||
profileName.textContent = name;
|
||||
profileID.textContent = displayID
|
||||
? t('rechargeReward.idPrefix', 'ID:') + ' ' + displayID
|
||||
: '';
|
||||
profileRecharge.textContent =
|
||||
t('rechargeReward.accumulatedRecharge', 'Accumulated Recharge:') +
|
||||
wholeUSD(totalMinor);
|
||||
if (avatar) {
|
||||
profileAvatar.src = avatar;
|
||||
profileAvatar.hidden = false;
|
||||
} else {
|
||||
profileAvatar.removeAttribute('src');
|
||||
profileAvatar.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCountdown() {
|
||||
if (!countdownEndAt) {
|
||||
countdown.classList.remove('has-values');
|
||||
Object.keys(countNodes).forEach(function (key) {
|
||||
countNodes[key].textContent = '';
|
||||
});
|
||||
return;
|
||||
}
|
||||
var remaining = Math.max(
|
||||
0,
|
||||
countdownEndAt - (Date.now() - timeOffsetMS)
|
||||
);
|
||||
var totalSeconds = Math.floor(remaining / 1000);
|
||||
countNodes.days.textContent = twoDigits(totalSeconds / 86400);
|
||||
countNodes.hours.textContent = twoDigits((totalSeconds % 86400) / 3600);
|
||||
countNodes.minutes.textContent = twoDigits((totalSeconds % 3600) / 60);
|
||||
countNodes.seconds.textContent = twoDigits(totalSeconds % 60);
|
||||
countdown.classList.add('has-values');
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
currentData = normalizeData(data);
|
||||
renderProfile(currentData);
|
||||
renderSections(currentData.tiers);
|
||||
timeOffsetMS = Date.now() - currentData.serverTimeMS;
|
||||
countdownEndAt = numberValue(
|
||||
currentData.period,
|
||||
['end_ms', 'endMs'],
|
||||
0
|
||||
);
|
||||
updateCountdown();
|
||||
if (countdownTimer) window.clearInterval(countdownTimer);
|
||||
if (countdownEndAt)
|
||||
countdownTimer = window.setInterval(updateCountdown, 1000);
|
||||
pageStatus.hidden = true;
|
||||
pageStatus.textContent = '';
|
||||
stage.setAttribute('aria-busy', 'false');
|
||||
}
|
||||
|
||||
function loadStatus(params) {
|
||||
if (shouldUseMock(params)) return Promise.resolve(mockData(params));
|
||||
if (!window.HyAppAPI || typeof window.HyAppAPI.get !== 'function') {
|
||||
return Promise.reject(new Error('api_unavailable'));
|
||||
}
|
||||
return window.HyAppAPI.get(STATUS_PATH);
|
||||
}
|
||||
|
||||
function showLoadError(error) {
|
||||
console.warn('[recharge-reward/fami] status load failed', error);
|
||||
currentData = null;
|
||||
rewardSections.innerHTML = '';
|
||||
profileName.textContent = '';
|
||||
profileID.textContent = '';
|
||||
profileRecharge.textContent = '';
|
||||
profileAvatar.hidden = true;
|
||||
pageStatus.textContent = t('rechargeReward.loadFailed', 'Load failed');
|
||||
pageStatus.hidden = false;
|
||||
stage.setAttribute('aria-busy', 'false');
|
||||
}
|
||||
|
||||
function init() {
|
||||
var params = queryParams();
|
||||
document.title = t('rechargeReward.pageTitle', 'Recharge Reward');
|
||||
updateScale();
|
||||
loadStatus(params).then(render).catch(showLoadError);
|
||||
|
||||
window.addEventListener('resize', updateScale);
|
||||
window.addEventListener('hyapp:i18n-ready', function () {
|
||||
document.title = t('rechargeReward.pageTitle', 'Recharge Reward');
|
||||
if (currentData) {
|
||||
// 语言包只影响 DOM 文案;复用已归一化的业务状态,不能再次按原始 API 结构解析而丢失奖励卡。
|
||||
renderProfile(currentData);
|
||||
renderSections(currentData.tiers);
|
||||
updateCountdown();
|
||||
} else if (!pageStatus.hidden)
|
||||
pageStatus.textContent = t(
|
||||
'rechargeReward.loadFailed',
|
||||
'Load failed'
|
||||
);
|
||||
});
|
||||
|
||||
// 远程头像或奖励封面失效时只隐藏业务媒体,装饰框和真实金额状态仍保留,避免回退到伪造素材。
|
||||
stage.addEventListener(
|
||||
'error',
|
||||
function (event) {
|
||||
if (
|
||||
event.target === profileAvatar ||
|
||||
(event.target &&
|
||||
event.target.classList.contains('reward-media'))
|
||||
) {
|
||||
event.target.hidden = true;
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init, { once: true });
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||