月度工资查看

This commit is contained in:
zhx 2026-06-01 21:00:03 +08:00
parent d33cbbcdac
commit 4bae0aca72
9 changed files with 476 additions and 45 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>BD Center</title>
<link rel="stylesheet" href="./style.css?v=20260429-0300" />
<link rel="stylesheet" href="./style.css?v=20260601-1900" />
</head>
<body>
<div class="bd-center" aria-label="BD Center" data-i18n-aria="page_label" data-loading="true">
@ -69,6 +69,13 @@
<div class="team-list-tabs is-single" role="tablist" aria-label="Team lists">
<button class="team-list-tab is-active" type="button" role="tab" aria-selected="true" data-list-tab="agency" data-i18n="agency_list_link">Agency List</button>
</div>
<div class="team-list-period">
<label class="month-filter">
<span data-i18n="month_label">Month</span>
<input id="billMonthInput" type="month" data-i18n-aria="month_label" aria-label="Month" />
</label>
<div class="bill-range" id="billRange"></div>
</div>
<div class="team-list-summary" id="teamListSummary"></div>
<div class="team-list-content" id="teamListContent">
<div class="empty-state" data-i18n="loading_data">Loading...</div>
@ -116,6 +123,6 @@
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
</div>
<script src="./script.js?v=20260529-1300" defer></script>
<script src="./script.js?v=20260601-1900" defer></script>
</body>
</html>

View File

@ -22,6 +22,7 @@
bd_number: "BD number",
team_salary: "Team salary",
agency_list_link: "Agency List",
month_label: "Month",
invite_become_agent: "Invite Agent",
invite_agent_title: "Invite Agent",
enter_user_id: "Enter User ID",
@ -65,6 +66,7 @@
bd_number: "عدد BD",
team_salary: "راتب الفريق",
agency_list_link: "قائمة الوكالات",
month_label: "الشهر",
invite_become_agent: "دعوة وكيل",
invite_agent_title: "دعوة وكيل",
enter_user_id: "أدخل معرف المستخدم",
@ -104,6 +106,7 @@
bd_number: "BD sayısı",
team_salary: "Takım maaşı",
agency_list_link: "Ajans listesi",
month_label: "Ay",
invite_become_agent: "Ajans Davet Et",
invite_agent_title: "Ajans Davet Et",
enter_user_id: "Kullanıcı ID gir",
@ -143,6 +146,7 @@
bd_number: "Jumlah BD",
team_salary: "Gaji tim",
agency_list_link: "Daftar agensi",
month_label: "Bulan",
invite_become_agent: "Invite Agent",
invite_agent_title: "Invite Agent",
enter_user_id: "Masukkan ID Pengguna",
@ -176,7 +180,7 @@
profile: "/team/member/profile",
identity: "/app/h5/identity",
balance: "/wallet/salary-account/balance?salaryType=BD_SALARY",
agencyBill: "/team/bd/member-bill/list?type=BD",
agencyBill: (billBelong) => `/team/bd/member-bill/list?type=BD&billBelong=${encodeURIComponent(billBelong)}`,
inviteList: "/team/bd/invite-message-own",
inviteSearch: (account) => `/user/user-profile/open-search?account=${encodeURIComponent(account)}`,
inviteAgent: "/team/bd/invite-agent",
@ -190,6 +194,7 @@
balanceTotal: {},
bill: {},
agencyBill: null,
billMonth: normalizeMonthValue(params.get("month") || params.get("billBelong")) || getCurrentMonthValue(),
teamList: {
type: "agency",
loading: false,
@ -253,6 +258,52 @@
return supportedLanguages.includes(value) ? value : "en";
}
function getCurrentMonthValue() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
return `${year}-${month}`;
}
function normalizeMonthValue(value) {
const raw = String(value || "").trim();
let year = "";
let month = "";
if (/^\d{4}-\d{2}$/.test(raw)) {
[year, month] = raw.split("-");
} else if (/^\d{6}$/.test(raw)) {
year = raw.slice(0, 4);
month = raw.slice(4, 6);
} else if (/^\d{8}$/.test(raw)) {
year = raw.slice(0, 4);
month = raw.slice(4, 6);
}
const numericYear = Number(year);
const numericMonth = Number(month);
if (!Number.isInteger(numericYear) || !Number.isInteger(numericMonth)) return "";
if (numericYear < 2000 || numericYear > 2099 || numericMonth < 1 || numericMonth > 12) return "";
return `${year}-${String(numericMonth).padStart(2, "0")}`;
}
function monthValueToBillBelong(value) {
return normalizeMonthValue(value).replace("-", "");
}
function syncMonthControl() {
const input = document.querySelector("#billMonthInput");
if (!input) return;
input.max = getCurrentMonthValue();
input.value = state.billMonth;
}
function renderBillRange(data = teamListBill()) {
const range = document.querySelector("#billRange");
if (!range) return;
range.textContent = data?.billTitle || state.billMonth.replace("-", ".");
}
function message(key, fallback = "") {
return currentMessages[key] || fallbackMessages.en[key] || fallback || key;
}
@ -577,6 +628,8 @@
const members = Array.isArray(data.memberBillList) ? data.memberBillList : [];
const summary = document.querySelector("#teamListSummary");
const content = document.querySelector("#teamListContent");
syncMonthControl();
renderBillRange(data);
document.querySelectorAll("[data-list-tab]").forEach((button) => {
const active = normalizeTeamListType(button.dataset.listTab) === type;
button.classList.toggle("is-active", active);
@ -933,11 +986,37 @@
}
async function fetchAgencyBill() {
state.agencyBill = await requestJSON(bdCenterEndpoints.agencyBill) || {};
state.agencyBill = await requestJSON(bdCenterEndpoints.agencyBill(monthValueToBillBelong(state.billMonth))) || {};
renderBill();
renderTeamListCard();
}
async function handleBillMonthChange(event) {
const nextMonth = normalizeMonthValue(event.target.value);
if (!nextMonth) {
syncMonthControl();
return;
}
if (nextMonth === state.billMonth) return;
state.billMonth = nextMonth;
state.agencyBill = null;
state.teamList.error = "";
state.teamList.loading = true;
renderTeamListCard();
try {
await fetchAgencyBill();
} catch (error) {
console.error("Failed to load selected bill month:", error);
state.teamList.error = error.response?.errorMsg || error.message || message("failed_to_load");
showToast(state.teamList.error);
} finally {
state.teamList.loading = false;
renderTeamListCard();
}
}
async function ensureTeamListData(type) {
const normalizedType = normalizeTeamListType(type);
if (hasTeamListData(teamListBill(normalizedType))) return;
@ -1083,6 +1162,7 @@
document.querySelectorAll("[data-list-tab]").forEach((button) => {
button.addEventListener("click", () => switchTeamList(button.dataset.listTab));
});
document.querySelector("#billMonthInput")?.addEventListener("change", handleBillMonthChange);
document.querySelector("[data-action='invite-agent']")?.addEventListener("click", () => showInviteModal("agent"));
document.querySelector("#inviteSearchInput")?.addEventListener("input", handleInviteInput);
document.querySelector("#inviteSearchForm")?.addEventListener("submit", searchInviteUser);

View File

@ -412,6 +412,55 @@ button {
box-shadow: 0 8px 18px rgba(21, 189, 169, 0.24);
}
.team-list-period {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 10px;
}
.month-filter {
display: grid;
min-width: 0;
gap: 6px;
}
.month-filter span {
color: #8b8d92;
font-size: 11px;
font-weight: 850;
line-height: 1.2;
}
.month-filter input {
width: 100%;
min-width: 0;
height: 38px;
padding: 0 10px;
border: 1px solid #e4e8eb;
border-radius: 8px;
outline: none;
background: #fbfcfc;
color: #24282e;
font: inherit;
font-size: 13px;
font-weight: 900;
}
.month-filter input:focus {
border-color: rgba(16, 188, 176, 0.72);
box-shadow: 0 0 0 3px rgba(67, 231, 216, 0.16);
}
.bill-range {
min-width: 0;
color: #8b8d92;
font-size: 11px;
font-weight: 850;
line-height: 1.25;
text-align: right;
}
.team-list-summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>Withdraw/Exchange</title>
<link rel="stylesheet" href="./style.css?v=20260529-0100" />
<link rel="stylesheet" href="./style.css?v=20260601-0110" />
</head>
<body>
<div class="wallet-page" aria-label="Withdraw and exchange" data-i18n-aria="page_label">
@ -165,6 +165,6 @@
</div>
<div class="home-indicator" aria-hidden="true"></div>
</div>
<script src="./script.js?v=20260601-0100" defer></script>
<script src="./script.js?v=20260601-0110" defer></script>
</body>
</html>

View File

@ -694,7 +694,190 @@
return message("available_income", "salary");
}
function salaryHistoryCode(item) {
const candidates = [
item?.salaryEvent,
item?.event,
item?.status,
item?.title,
item?.eventDesc,
item?.eventDescribe,
item?.salaryTypeName
];
for (const candidate of candidates) {
const value = String(candidate || "").trim().toUpperCase();
if (value) return value;
}
return "";
}
function isAgencySalaryHistoryItem(item) {
const code = salaryHistoryCode(item);
const salaryType = normalizeSalaryType(item?.salaryType);
if (
code.includes("SETTLEMENT_WAGES_AGENT") ||
code.includes("SETTLEMENT_WAGES_MEMBER") ||
code.includes("BD_SALARY_SETTLEMENT") ||
code.includes("BD_LEADER_SALARY_SETTLEMENT") ||
code.includes("ADMIN_SALARY_SETTLEMENT")
) {
return true;
}
return code === "ISSUE" && salaryType && salaryType !== "HOST_SALARY";
}
function isHostSalaryHistoryItem(item) {
if (isAgencySalaryHistoryItem(item)) return false;
const code = salaryHistoryCode(item);
const salaryType = normalizeSalaryType(item?.salaryType);
if (code === "SYSTEM_SETTLEMENT_WAGES" || code === "SYSTEM_AUTOMATIC_SETTLEMENT_WAGES" || code === "SEND_SALARY") {
return true;
}
if (code === "ISSUE" && salaryType === "HOST_SALARY") return true;
return code.includes("SETTLEMENT_WAGES") || code.includes("SETTLEMENT WAGES");
}
function parseHistoryObject(value) {
if (!value) return null;
if (typeof value === "object") return value;
if (typeof value !== "string") return null;
const text = value.trim();
if (!text || !text.startsWith("{")) return null;
try {
return JSON.parse(text);
} catch (error) {
return null;
}
}
function cleanHistoryName(value) {
const text = String(value || "").trim();
if (!text || text === "null" || text === "undefined" || /^\d+$/.test(text)) return "";
return text;
}
function cleanHistoryAvatar(value) {
const text = String(value || "").trim();
if (!text || text === "null" || text === "undefined" || /^\d+$/.test(text)) return "";
return text;
}
function historyNameFromObject(source) {
const object = parseHistoryObject(source);
if (!object) return "";
const keys = [
"providerName",
"agencyName",
"agentName",
"fromUserName",
"sourceName",
"senderName",
"userNickname",
"userNickName",
"nickName",
"nickname",
"name",
"account"
];
for (const key of keys) {
const name = cleanHistoryName(object[key]);
if (name) return name;
}
const nestedKeys = ["provider", "agency", "agent", "fromUser", "sourceUser", "sender", "user", "acceptUser", "acceptUserAccount", "profile"];
for (const key of nestedKeys) {
const name = historyNameFromObject(object[key]);
if (name) return name;
}
return "";
}
function historyAvatarFromObject(source) {
const object = parseHistoryObject(source);
if (!object) return "";
const keys = [
"userAvatar",
"avatar",
"avatarUrl",
"avatarURL",
"headPic",
"headImg",
"portrait",
"photo",
"profileImage",
"image"
];
for (const key of keys) {
const avatar = cleanHistoryAvatar(object[key]);
if (avatar) return avatar;
}
const nestedKeys = ["provider", "agency", "agent", "fromUser", "sourceUser", "sender", "user", "acceptUser", "acceptUserAccount", "profile"];
for (const key of nestedKeys) {
const avatar = historyAvatarFromObject(object[key]);
if (avatar) return avatar;
}
return "";
}
function agencySalaryProviderName(item) {
return historyNameFromObject(item) ||
historyNameFromObject(item?.details) ||
historyNameFromObject(item?.detail) ||
historyNameFromObject(item?.raw) ||
"Agency";
}
function agencySalaryProviderAvatar(item) {
return historyAvatarFromObject(item) ||
historyAvatarFromObject(item?.details) ||
historyAvatarFromObject(item?.detail) ||
historyAvatarFromObject(item?.raw) ||
"";
}
function agencySalaryDescription(item) {
if (!isAgencySalaryHistoryItem(item)) return "";
return `${agencySalaryProviderName(item)} provided you $${formatMoney(item.amount)} salary`;
}
function createAgencySalaryDescription(item) {
const text = agencySalaryDescription(item);
if (!text) return null;
const name = agencySalaryProviderName(item);
const avatarURL = agencySalaryProviderAvatar(item);
const description = document.createElement("div");
description.className = "salary-history-item-desc";
const avatar = document.createElement("span");
avatar.className = "salary-history-desc-avatar";
if (avatarURL) {
const image = document.createElement("img");
image.src = avatarURL;
image.alt = "";
avatar.appendChild(image);
} else {
avatar.textContent = (name.slice(0, 1) || "A").toUpperCase();
}
const copy = document.createElement("span");
copy.className = "salary-history-item-desc-text";
copy.textContent = text;
description.append(avatar, copy);
return description;
}
function shouldShowHistoryStatus(item) {
return Boolean(item.status && !isAgencySalaryHistoryItem(item) && !isHostSalaryHistoryItem(item));
}
function historyItemTitle(item) {
if (isAgencySalaryHistoryItem(item)) return "Agency salary";
if (isHostSalaryHistoryItem(item)) return "Host salary";
return item.title ||
item.eventDesc ||
item.eventDescribe ||
@ -715,6 +898,8 @@
title.className = "salary-history-item-title";
title.textContent = historyItemTitle(item);
const description = createAgencySalaryDescription(item);
const meta = document.createElement("div");
meta.className = "salary-history-item-meta";
const metaItems = [];
@ -727,7 +912,9 @@
}
meta.textContent = metaItems.join(" ");
main.append(title, meta);
main.append(title);
if (description) main.appendChild(description);
main.appendChild(meta);
const isOut = Number(item.type) === 1;
const amount = document.createElement("div");
@ -735,7 +922,7 @@
amount.textContent = `${isOut ? "-" : "+"}$${formatMoney(item.amount)}`;
row.append(main, amount);
if (item.status) {
if (shouldShowHistoryStatus(item)) {
const status = document.createElement("div");
status.className = "salary-history-status";
status.textContent = item.status;
@ -866,6 +1053,15 @@
items: records.map((item) => ({
id: item.id,
title: item.eventDesc || item.salaryTypeName || salaryTypeLabel(item.salaryType),
eventDesc: item.eventDesc,
eventDescribe: item.eventDescribe,
salaryEvent: item.salaryEvent,
event: item.event || item.salaryEvent,
salaryType: item.salaryType || activeSalaryType(),
salaryTypeName: item.salaryTypeName,
details: item.details || item.detail,
detail: item.detail,
raw: item,
amount: item.amount,
balance: item.availableBalance ?? item.balance,
type: item.type,
@ -884,6 +1080,11 @@
items: records.map((item) => ({
id: item.id,
title: item.eventDescribe || item.event || message("salary_history", "Salary history"),
event: item.event,
eventDescribe: item.eventDescribe,
details: item.details || item.detail,
detail: item.detail,
raw: item,
amount: item.amount,
balance: item.balance,
type: item.type,

View File

@ -783,6 +783,48 @@ select {
white-space: nowrap;
}
.salary-history-item-desc {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
margin-top: 5px;
color: var(--muted);
font-size: 12px;
font-weight: 800;
line-height: 1.25;
}
.salary-history-desc-avatar {
display: inline-flex;
flex: 0 0 18px;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
overflow: hidden;
border-radius: 50%;
background: #e5f7f5;
color: #0d8f83;
font-size: 9px;
font-weight: 950;
line-height: 1;
}
.salary-history-desc-avatar img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.salary-history-item-desc-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.salary-history-item-meta {
display: flex;
flex-wrap: wrap;

View File

@ -8,7 +8,7 @@
/>
<meta name="format-detection" content="telephone=no" />
<title>Recharge Center</title>
<link rel="stylesheet" href="./style.css?v=20260601-0400" />
<link rel="stylesheet" href="./style.css?v=20260601-0600" />
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
</head>
<body>
@ -219,6 +219,6 @@
</div>
</div>
<script src="./script.js?v=20260601-0400"></script>
<script src="./script.js?v=20260601-0600"></script>
</body>
</html>

View File

@ -141,7 +141,7 @@
if (window.location.protocol === "file:") return fallbackMessages[normalizedLang] || fallbackMessages.en;
try {
const response = await fetch(`./locales/${normalizedLang}.json?v=20260601-0400`, { cache: "no-store" });
const response = await fetch(`./locales/${normalizedLang}.json?v=20260601-0600`, { cache: "no-store" });
if (response.ok) {
const data = await response.json();
return { ...fallbackMessages.en, ...data };
@ -526,6 +526,12 @@
return value.includes("TRC20") && (value.includes("USDT") || value.includes("UDST"));
}
function isBankTransferChannel(item) {
const code = String(channelCode(item) || "").trim().toUpperCase();
const value = `${code} ${channelName(item)} ${item?.details?.factoryChannel || ""}`.toUpperCase();
return code === "BANK" || value.includes("BANK TRANSFER") || value.includes("BANKTRANSFER") || value.includes("BANK_TRANSFER");
}
function isSuccessfulPaymentStatus(data) {
if (data?.success === true || data?.paid === true || data?.paySuccess === true || data?.orderPaid === true) return true;
const status = String(data?.status || data?.orderStatus || data?.payStatus || data?.tradeStatus || "").trim().toUpperCase();
@ -675,8 +681,12 @@
{ id: "g3", content: "100,000 coins", amountUsd: 10, awardContent: "+12,000 bonus" }
],
channels: [
{ channel: { channelCode: "UPI", channelName: "UPI" } },
{ channel: { channelCode: "BANK", channelName: "Bank Transfer" } },
{ channel: { channelCode: "UPI_BHIM", channelName: "BHIM UPI", channelIcon: "../mifa-pay/logos/bhim.png" } },
{ channel: { channelCode: "UPI_PHONEPE", channelName: "PhonePe", channelIcon: "../mifa-pay/logos/phonepe.png" } },
{ channel: { channelCode: "UPI_GOOGLEPAY", channelName: "Google Pay", channelIcon: "../mifa-pay/logos/googlepay.png" } },
{ channel: { channelCode: "UPI_PAYTM", channelName: "Paytm", channelIcon: "../mifa-pay/logos/paytm.png" } },
{ channel: { channelCode: "UPI_MOBIKWIK", channelName: "Mobikwik", channelIcon: "../mifa-pay/logos/mobikwik.png" } },
{ channel: { channelCode: "UPI", channelName: "UPI", channelIcon: "../mifa-pay/logos/upi.png" } },
{
channel: { channelCode: "USDT_TRC20", channelName: "USDT TRC20" },
details: { trc20Address: "TMockTrc20RechargeAddress000000000001" }
@ -775,8 +785,7 @@
},
visibleChannels() {
const usdtTrc20 = this.channels.filter(isUsdtTrc20Channel);
if (this.isDealer) return usdtTrc20;
const thirdParty = this.channels.filter((channel) => !isUsdtChannel(channel));
const thirdParty = this.channels.filter((channel) => !isUsdtChannel(channel) && !isBankTransferChannel(channel));
return [...thirdParty, ...usdtTrc20];
}
},

View File

@ -381,8 +381,8 @@ button:disabled {
display: flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
width: 36px;
height: 36px;
overflow: hidden;
border-radius: 8px;
background: #eaf7f5;
@ -408,11 +408,12 @@ button:disabled {
.method-badge {
min-height: 22px;
border-radius: 999px;
padding: 3px 8px;
padding: 3px 6px;
background: #fff0c2;
color: #8a5c00;
font-size: 11px;
font-size: 10px;
font-weight: 900;
white-space: nowrap;
}
.payment-head {
@ -421,18 +422,20 @@ button:disabled {
.package-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.package-button {
display: grid;
justify-items: center;
gap: 4px;
min-height: 106px;
align-content: center;
gap: 5px;
min-width: 0;
min-height: 94px;
border: 1px solid var(--line);
border-radius: 8px;
padding: 10px 8px;
padding: 9px 6px;
background: #fff;
color: var(--text);
}
@ -444,22 +447,20 @@ button:disabled {
}
.coin-mark {
width: 26px;
height: 26px;
display: block;
width: 28px;
height: 28px;
border-radius: 50%;
background:
radial-gradient(circle at 35% 26%, #fff8d8 0 18%, transparent 20%),
linear-gradient(145deg, #ffd963, #f0a516);
box-shadow: inset 0 -2px 0 rgba(133, 85, 0, 0.16);
background: url("../smash-golden-egg/assets/common/yumi_coin.png") center / contain no-repeat;
filter: drop-shadow(0 2px 3px rgba(145, 91, 0, 0.18));
}
.package-button strong {
max-width: 100%;
overflow: hidden;
font-size: 14px;
overflow-wrap: anywhere;
font-size: 12px;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.package-button span {
@ -470,9 +471,10 @@ button:disabled {
.package-button small {
max-width: 100%;
min-height: 12px;
overflow: hidden;
color: #b7791f;
font-size: 11px;
font-size: 10px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
@ -480,27 +482,35 @@ button:disabled {
.channel-list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.channel-button {
display: grid;
grid-template-columns: 42px minmax(0, 1fr) auto;
grid-template-columns: minmax(0, 1fr);
align-items: center;
gap: 10px;
justify-items: center;
gap: 6px;
width: 100%;
min-height: 62px;
min-height: 104px;
border: 1px solid var(--line);
border-radius: 8px;
padding: 10px;
padding: 10px 6px;
background: var(--surface-soft);
color: var(--text);
text-align: left;
text-align: center;
}
.channel-button.usdt {
grid-column: 1 / -1;
grid-template-columns: 36px minmax(0, 1fr) auto;
justify-items: stretch;
min-height: 68px;
padding: 10px 12px;
border-color: rgba(245, 189, 65, 0.7);
background: linear-gradient(180deg, #fffaf0, #fff);
text-align: left;
}
.channel-button.active {
@ -520,11 +530,20 @@ button:disabled {
.channel-copy h4 {
margin: 0;
overflow: hidden;
font-size: 15px;
overflow-wrap: anywhere;
font-size: 12px;
font-weight: 900;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.15;
text-align: center;
}
.channel-button.usdt .channel-copy {
align-self: center;
}
.channel-button.usdt .channel-copy h4 {
font-size: 15px;
text-align: left;
}
.channel-copy p {
@ -550,6 +569,14 @@ button:disabled {
}
[dir="rtl"] .channel-button {
text-align: center;
}
[dir="rtl"] .channel-button.usdt {
text-align: right;
}
[dir="rtl"] .channel-button.usdt .channel-copy h4 {
text-align: right;
}
@ -765,7 +792,23 @@ button:disabled {
}
@media (max-width: 360px) {
.package-grid {
grid-template-columns: 1fr;
.package-grid,
.channel-list {
gap: 6px;
}
.package-button {
min-height: 90px;
padding: 8px 4px;
}
.package-button strong,
.channel-copy h4 {
font-size: 11px;
}
.method-badge {
padding-inline: 5px;
font-size: 9px;
}
}