Lock phone UI to portrait with PWA and landscape gate.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+158
-149
@@ -1,149 +1,158 @@
|
||||
# 比特骆驼自动化对冲系统 — 更新说明
|
||||
|
||||
> 每次发版 / 合并重要改动后,在本文档顶部追加一条(日期新→旧)。
|
||||
> 关联审计结论写在同条「审计」小节。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-27 — 手机端期权卡 / 交易记录排版
|
||||
|
||||
### 变更
|
||||
|
||||
1. **期权持仓卡**:合约名与到期倒计时改为上下排,避免长 `inst_id` 被挤成竖排字。
|
||||
2. **交易记录**:列表项上下堆叠(组号/状态一行,净盈亏/平仓原因一行),明细成交行窄屏同样竖排。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-27 — 手机端监控布局
|
||||
|
||||
### 变更
|
||||
|
||||
1. **底部导航**:窄屏隐藏顶栏菜单,改用底栏「监控 / 记录 / 统计 / 设置」(含安全区)。
|
||||
2. **监控首页卡**:一屏看状态(启动/暂停/已停)、盘口、阶段、净盈利 vs 目标、到期倒计时、最近错误。
|
||||
3. **操作区**:启动 / 暂停 / 紧急全平触摸友好;紧急全平通栏;桌面布局不变。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 企业微信通知 + 暂停盯目标
|
||||
|
||||
### 变更
|
||||
|
||||
1. **企业微信**:独立模块 `backend/app/notify/wecom.py`;系统设置「运行模式」可配 Webhook / 开关 / 测试推送。
|
||||
2. **推送事件**:开仓、平仓、策略启动、暂停、故障(同类 5 分钟去重)。Markdown + 标识 `OPEN/CLOSE/START/PAUSE/FAULT`。
|
||||
3. **标题盘口**:模拟盘 →「模拟盘」;实盘 →「实盘·OKX」或「实盘·币安」。
|
||||
4. **暂停策略**:仍执行目标平仓 + 到期平仓,**不新开仓**(策略说明已同步)。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 一键部署环境检测 / 卸载删目录
|
||||
|
||||
### 变更
|
||||
|
||||
1. **一键部署 / 更新**:检测 Python3+venv、Node/npm、PM2;**已有则跳过**,缺失才 apt/npm 安装。
|
||||
2. **一键卸载**:备份 `.env` 与数据库后,**直接删除** `/opt/eth_hedge_sim`(并清理历史 `.removed` / `.old` 残留);不卸载系统级 Python/Node/PM2。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 自动备份 / 下载 / 上传恢复
|
||||
|
||||
### 变更
|
||||
|
||||
1. **自动备份**:默认开启,间隔 24h,保留 14 份;落盘目录优先 `/root/eth_hedge_backups`(可用 `BACKUP_DIR` 覆盖)。内容含 `hedge.db` + `.env`。
|
||||
2. **系统设置 → 备份恢复**:立即备份、服务器列表下载、上传 zip 恢复(确认串 `RESTORE`)。
|
||||
3. **新服务器迁移**:新机部署后上传旧机备份即可覆盖库与环境变量;恢复后进程退出由 PM2 拉起。有持仓时禁止恢复。
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest`:含 `test_backup` 等全量
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — LIVE 达标含估平仓费;手动/紧急全平进休息
|
||||
|
||||
### 变更
|
||||
|
||||
1. **LIVE 达标口径**:盯盘净利继续扣预估平仓手续费(与策略说明 / SIM 一致);实盘索引改为「含估平仓费」。
|
||||
2. **组间休息**:手动全平成功后调用 `enter_rest_after_close()`;紧急全平成功路径同样进入休息(与自动全平共用)。
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest` 全量
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — LIVE 对账 / 手动开仓 UI / Token 自动换发
|
||||
|
||||
### 变更
|
||||
|
||||
1. **开仓幂等与对账**:`opening` 占槽;LIVE 开仓前核对交易所永续(双侧);启动写 mismatch 日志。
|
||||
2. **平仓张数**:OKX/币安平永续优先读交易所持仓,失败才回退本地账本。
|
||||
3. **杠杆**:LIVE 开永续前 `set_leverage`(失败仅告警)。
|
||||
4. **自动运行禁手动开仓**:`running=1` 时 `/api/sim/open-group` 返回 409;计划页按钮禁用。
|
||||
5. **手动开仓显示**:策略设置「节奏」→「显示手动开仓」,**默认不显示**;关闭时 API 亦拒绝手动开/手动全平。紧急全平始终可用且为**红色**。
|
||||
6. **启动态**:策略运行中主按钮文案为「启动中」,绿色样式。
|
||||
7. **Token 自动换发**:HMAC Bearer(非 Flask)。新增 `POST /api/auth/refresh`;前端登录后定时/切回前台/临近过期静默换发。默认口令未改。
|
||||
|
||||
### 审计(本包改后)
|
||||
|
||||
| 项 | 状态 |
|
||||
|----|------|
|
||||
| 交易所↔本地对账 / 开仓幂等 | **已修** |
|
||||
| 平仓张数读交易所 | **已修** |
|
||||
| set_leverage | **已修**(失败告警) |
|
||||
| 自动运行仍可手动开仓 | **已修**(API + UI) |
|
||||
| 手动开/全平默认露出 | **已修**(默认隐藏 + 设置开关) |
|
||||
| 紧急全平样式 / 启动中文案 | **已修** |
|
||||
| Token 长期不轮换 | **已修**(refresh + 前端自动换发) |
|
||||
| 对账只查单侧漏 short | **已修**(flat 时 long+short) |
|
||||
| 放弃期权残留 / 模式切换仅本地 | **未做**(非本包;紧急全平/人工核对) |
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest`:52 passed
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 审计修复包(安全 / 下单 / 平仓)
|
||||
|
||||
### 变更
|
||||
|
||||
1. **LIVE 永续合约号**:开平仓统一用 `load_runtime_settings()` / 组内 `perp_inst_id`,修复 UI 切币安后仍用 env `ETH-USDT-SWAP` 的问题。
|
||||
2. **到期平仓**:期权先走交易所真实平仓(失败且仓位已空才本地结算),并在永续失败前写入 `option_closed_perp_pending`。
|
||||
3. **成交完整性**:OKX/币安仅接受完全成交;账本数量按交易所 `accFillSz`/`executedQty` 回写。
|
||||
4. **`ctVal`**:查不到合约面值时直接失败,禁止静默默认 `0.01`。
|
||||
5. **放弃期权路径**:优先交易所卖期权;仅深虚且卖不出时才残留归档。
|
||||
6. **LIVE 重启**:启动时若为 LIVE,自动暂停策略,须人工再点启动。
|
||||
7. **安全**:登录限流;切 LIVE 须服务端校验确认串 `LIVE`;生产关闭 `/docs`;改密后抬升 token 版本作废旧票;`.env` 写入后尽量 `chmod 600`;CORS 改为同源优先。
|
||||
8. **出场**:LIVE 净利加入保守平仓手续费缓冲,减少「刚达标但扣费后不够」的提前出场。
|
||||
|
||||
### 审计(本包改后复审)
|
||||
|
||||
| 项 | 状态 |
|
||||
|----|------|
|
||||
| 永续符号 env/DB 不一致 | **已修**(`symbols.resolve_perp_inst_id` + 开平仓全路径) |
|
||||
| 到期只本地结算期权 | **已修**(先交易所卖;失败才本地结算) |
|
||||
| 到期永续失败无 pending | **已修**(统一 `_mark_option_closed_perp_pending`) |
|
||||
| partially_filled 当完成 | **已修**(OKX 仅 `filled`;币安仅 `FILLED`) |
|
||||
| 放弃路径不卖期权 | **已缓解**(先 `close_group` 双腿;失败且深虚才残留) |
|
||||
| 开仓崩溃双开 | **部分**:LIVE 启动强制 `running=0`;仍缺交易所对账 |
|
||||
| ctVal 静默默认 | **已修**(查不到直接报错) |
|
||||
| LIVE 出场不计平仓费 | **已修**(保守 `est_close_fees`) |
|
||||
| 登录无限流 / LIVE 可 API 绕过确认 | **已修**(限流 + `confirm_live_phrase=LIVE`) |
|
||||
| `/docs` 公开 / CORS `*` / mask 泄露后缀 | **已修** |
|
||||
| 改密后旧 token | **已修**(`AUTH_TOKEN_VERSION`) |
|
||||
| 默认口令 / 公网 TLS | **未改默认口令**(部署须改 `.env`);TLS 仍靠反代 |
|
||||
| 杠杆 set_leverage | **未做** |
|
||||
| 平仓张数读交易所持仓 | **未做** |
|
||||
|
||||
### 残留风险(下次优先)
|
||||
|
||||
1. 启动时交易所↔本地仓位对账与开仓幂等键
|
||||
2. 平仓前查询交易所实际持仓张数
|
||||
3. 开仓前 `set_leverage`
|
||||
4. 可选:拒绝默认 `AUTH_*` 启动(硬开关)
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest`:46 passed(含 `test_symbols`、脱敏用例更新)
|
||||
|
||||
---
|
||||
# 比特骆驼自动化对冲系统 — 更新说明
|
||||
|
||||
> 每次发版 / 合并重要改动后,在本文档顶部追加一条(日期新→旧)。
|
||||
> 关联审计结论写在同条「审计」小节。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-27 — 手机端竖屏锁定
|
||||
|
||||
### 变更
|
||||
|
||||
1. **PWA**:`manifest.webmanifest` 的 `orientation` 改为 `portrait`(安装到主屏幕后系统侧竖屏)。
|
||||
2. **浏览器**:手机横屏时全屏提示「请竖屏使用」;支持时尝试 `screen.orientation.lock`。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-27 — 手机端期权卡 / 交易记录排版
|
||||
|
||||
### 变更
|
||||
|
||||
1. **期权持仓卡**:合约名与到期倒计时改为上下排,避免长 `inst_id` 被挤成竖排字。
|
||||
2. **交易记录**:列表项上下堆叠(组号/状态一行,净盈亏/平仓原因一行),明细成交行窄屏同样竖排。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-27 — 手机端监控布局
|
||||
|
||||
### 变更
|
||||
|
||||
1. **底部导航**:窄屏隐藏顶栏菜单,改用底栏「监控 / 记录 / 统计 / 设置」(含安全区)。
|
||||
2. **监控首页卡**:一屏看状态(启动/暂停/已停)、盘口、阶段、净盈利 vs 目标、到期倒计时、最近错误。
|
||||
3. **操作区**:启动 / 暂停 / 紧急全平触摸友好;紧急全平通栏;桌面布局不变。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 企业微信通知 + 暂停盯目标
|
||||
|
||||
### 变更
|
||||
|
||||
1. **企业微信**:独立模块 `backend/app/notify/wecom.py`;系统设置「运行模式」可配 Webhook / 开关 / 测试推送。
|
||||
2. **推送事件**:开仓、平仓、策略启动、暂停、故障(同类 5 分钟去重)。Markdown + 标识 `OPEN/CLOSE/START/PAUSE/FAULT`。
|
||||
3. **标题盘口**:模拟盘 →「模拟盘」;实盘 →「实盘·OKX」或「实盘·币安」。
|
||||
4. **暂停策略**:仍执行目标平仓 + 到期平仓,**不新开仓**(策略说明已同步)。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 一键部署环境检测 / 卸载删目录
|
||||
|
||||
### 变更
|
||||
|
||||
1. **一键部署 / 更新**:检测 Python3+venv、Node/npm、PM2;**已有则跳过**,缺失才 apt/npm 安装。
|
||||
2. **一键卸载**:备份 `.env` 与数据库后,**直接删除** `/opt/eth_hedge_sim`(并清理历史 `.removed` / `.old` 残留);不卸载系统级 Python/Node/PM2。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 自动备份 / 下载 / 上传恢复
|
||||
|
||||
### 变更
|
||||
|
||||
1. **自动备份**:默认开启,间隔 24h,保留 14 份;落盘目录优先 `/root/eth_hedge_backups`(可用 `BACKUP_DIR` 覆盖)。内容含 `hedge.db` + `.env`。
|
||||
2. **系统设置 → 备份恢复**:立即备份、服务器列表下载、上传 zip 恢复(确认串 `RESTORE`)。
|
||||
3. **新服务器迁移**:新机部署后上传旧机备份即可覆盖库与环境变量;恢复后进程退出由 PM2 拉起。有持仓时禁止恢复。
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest`:含 `test_backup` 等全量
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — LIVE 达标含估平仓费;手动/紧急全平进休息
|
||||
|
||||
### 变更
|
||||
|
||||
1. **LIVE 达标口径**:盯盘净利继续扣预估平仓手续费(与策略说明 / SIM 一致);实盘索引改为「含估平仓费」。
|
||||
2. **组间休息**:手动全平成功后调用 `enter_rest_after_close()`;紧急全平成功路径同样进入休息(与自动全平共用)。
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest` 全量
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — LIVE 对账 / 手动开仓 UI / Token 自动换发
|
||||
|
||||
### 变更
|
||||
|
||||
1. **开仓幂等与对账**:`opening` 占槽;LIVE 开仓前核对交易所永续(双侧);启动写 mismatch 日志。
|
||||
2. **平仓张数**:OKX/币安平永续优先读交易所持仓,失败才回退本地账本。
|
||||
3. **杠杆**:LIVE 开永续前 `set_leverage`(失败仅告警)。
|
||||
4. **自动运行禁手动开仓**:`running=1` 时 `/api/sim/open-group` 返回 409;计划页按钮禁用。
|
||||
5. **手动开仓显示**:策略设置「节奏」→「显示手动开仓」,**默认不显示**;关闭时 API 亦拒绝手动开/手动全平。紧急全平始终可用且为**红色**。
|
||||
6. **启动态**:策略运行中主按钮文案为「启动中」,绿色样式。
|
||||
7. **Token 自动换发**:HMAC Bearer(非 Flask)。新增 `POST /api/auth/refresh`;前端登录后定时/切回前台/临近过期静默换发。默认口令未改。
|
||||
|
||||
### 审计(本包改后)
|
||||
|
||||
| 项 | 状态 |
|
||||
|----|------|
|
||||
| 交易所↔本地对账 / 开仓幂等 | **已修** |
|
||||
| 平仓张数读交易所 | **已修** |
|
||||
| set_leverage | **已修**(失败告警) |
|
||||
| 自动运行仍可手动开仓 | **已修**(API + UI) |
|
||||
| 手动开/全平默认露出 | **已修**(默认隐藏 + 设置开关) |
|
||||
| 紧急全平样式 / 启动中文案 | **已修** |
|
||||
| Token 长期不轮换 | **已修**(refresh + 前端自动换发) |
|
||||
| 对账只查单侧漏 short | **已修**(flat 时 long+short) |
|
||||
| 放弃期权残留 / 模式切换仅本地 | **未做**(非本包;紧急全平/人工核对) |
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest`:52 passed
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-26 — 审计修复包(安全 / 下单 / 平仓)
|
||||
|
||||
### 变更
|
||||
|
||||
1. **LIVE 永续合约号**:开平仓统一用 `load_runtime_settings()` / 组内 `perp_inst_id`,修复 UI 切币安后仍用 env `ETH-USDT-SWAP` 的问题。
|
||||
2. **到期平仓**:期权先走交易所真实平仓(失败且仓位已空才本地结算),并在永续失败前写入 `option_closed_perp_pending`。
|
||||
3. **成交完整性**:OKX/币安仅接受完全成交;账本数量按交易所 `accFillSz`/`executedQty` 回写。
|
||||
4. **`ctVal`**:查不到合约面值时直接失败,禁止静默默认 `0.01`。
|
||||
5. **放弃期权路径**:优先交易所卖期权;仅深虚且卖不出时才残留归档。
|
||||
6. **LIVE 重启**:启动时若为 LIVE,自动暂停策略,须人工再点启动。
|
||||
7. **安全**:登录限流;切 LIVE 须服务端校验确认串 `LIVE`;生产关闭 `/docs`;改密后抬升 token 版本作废旧票;`.env` 写入后尽量 `chmod 600`;CORS 改为同源优先。
|
||||
8. **出场**:LIVE 净利加入保守平仓手续费缓冲,减少「刚达标但扣费后不够」的提前出场。
|
||||
|
||||
### 审计(本包改后复审)
|
||||
|
||||
| 项 | 状态 |
|
||||
|----|------|
|
||||
| 永续符号 env/DB 不一致 | **已修**(`symbols.resolve_perp_inst_id` + 开平仓全路径) |
|
||||
| 到期只本地结算期权 | **已修**(先交易所卖;失败才本地结算) |
|
||||
| 到期永续失败无 pending | **已修**(统一 `_mark_option_closed_perp_pending`) |
|
||||
| partially_filled 当完成 | **已修**(OKX 仅 `filled`;币安仅 `FILLED`) |
|
||||
| 放弃路径不卖期权 | **已缓解**(先 `close_group` 双腿;失败且深虚才残留) |
|
||||
| 开仓崩溃双开 | **部分**:LIVE 启动强制 `running=0`;仍缺交易所对账 |
|
||||
| ctVal 静默默认 | **已修**(查不到直接报错) |
|
||||
| LIVE 出场不计平仓费 | **已修**(保守 `est_close_fees`) |
|
||||
| 登录无限流 / LIVE 可 API 绕过确认 | **已修**(限流 + `confirm_live_phrase=LIVE`) |
|
||||
| `/docs` 公开 / CORS `*` / mask 泄露后缀 | **已修** |
|
||||
| 改密后旧 token | **已修**(`AUTH_TOKEN_VERSION`) |
|
||||
| 默认口令 / 公网 TLS | **未改默认口令**(部署须改 `.env`);TLS 仍靠反代 |
|
||||
| 杠杆 set_leverage | **未做** |
|
||||
| 平仓张数读交易所持仓 | **未做** |
|
||||
|
||||
### 残留风险(下次优先)
|
||||
|
||||
1. 启动时交易所↔本地仓位对账与开仓幂等键
|
||||
2. 平仓前查询交易所实际持仓张数
|
||||
3. 开仓前 `set_leverage`
|
||||
4. 可选:拒绝默认 `AUTH_*` 启动(硬开关)
|
||||
|
||||
### 测试
|
||||
|
||||
- `pytest`:46 passed(含 `test_symbols`、脱敏用例更新)
|
||||
|
||||
---
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"display_override": ["standalone", "minimal-ui", "browser"],
|
||||
"orientation": "any",
|
||||
"orientation": "portrait",
|
||||
"background_color": "#0b0e11",
|
||||
"theme_color": "#0b0e11",
|
||||
"lang": "zh-CN",
|
||||
|
||||
@@ -3,9 +3,11 @@ import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import { registerServiceWorker } from "./pwa/install";
|
||||
import { initPortraitLock } from "./pwa/portraitLock";
|
||||
import "./styles/app.css";
|
||||
|
||||
registerServiceWorker();
|
||||
initPortraitLock();
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/** 手机端尽量锁定竖屏;浏览器页用遮罩提示,已安装 PWA 由 manifest 约束。 */
|
||||
|
||||
type OrientationLockType =
|
||||
| "any"
|
||||
| "natural"
|
||||
| "landscape"
|
||||
| "portrait"
|
||||
| "portrait-primary"
|
||||
| "portrait-secondary"
|
||||
| "landscape-primary"
|
||||
| "landscape-secondary";
|
||||
|
||||
function isPhoneLike(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
const coarse = window.matchMedia("(pointer: coarse)").matches;
|
||||
const narrow = Math.min(window.screen.width, window.screen.height) <= 520;
|
||||
const ua = navigator.userAgent || "";
|
||||
const phoneUa = /Android.+Mobile|iPhone|iPod|Windows Phone/i.test(ua);
|
||||
return phoneUa || (coarse && narrow);
|
||||
}
|
||||
|
||||
async function tryLockPortrait(): Promise<void> {
|
||||
const orient = screen.orientation as ScreenOrientation & {
|
||||
lock?: (orientation: OrientationLockType) => Promise<void>;
|
||||
};
|
||||
if (!orient?.lock) return;
|
||||
try {
|
||||
await orient.lock("portrait");
|
||||
} catch {
|
||||
try {
|
||||
await orient.lock("portrait-primary");
|
||||
} catch {
|
||||
/* 多数浏览器仅全屏/PWA 允许 lock,失败则靠遮罩 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureGate(): HTMLElement {
|
||||
let el = document.getElementById("portrait-lock-gate");
|
||||
if (el) return el;
|
||||
el = document.createElement("div");
|
||||
el.id = "portrait-lock-gate";
|
||||
el.className = "portrait-lock-gate";
|
||||
el.setAttribute("aria-live", "polite");
|
||||
el.innerHTML =
|
||||
'<div class="portrait-lock-card">' +
|
||||
'<p class="portrait-lock-title">请竖屏使用</p>' +
|
||||
'<p class="portrait-lock-sub">手机端仅支持竖屏浏览</p>' +
|
||||
"</div>";
|
||||
document.body.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
function syncGate(): void {
|
||||
if (!isPhoneLike()) {
|
||||
const el = document.getElementById("portrait-lock-gate");
|
||||
if (el) el.classList.remove("show");
|
||||
return;
|
||||
}
|
||||
const landscape = window.matchMedia("(orientation: landscape)").matches;
|
||||
ensureGate().classList.toggle("show", landscape);
|
||||
if (!landscape) void tryLockPortrait();
|
||||
}
|
||||
|
||||
/** 注册竖屏锁定与横屏提示遮罩。 */
|
||||
export function initPortraitLock(): void {
|
||||
if (typeof window === "undefined") return;
|
||||
syncGate();
|
||||
window.addEventListener("orientationchange", syncGate);
|
||||
window.addEventListener("resize", syncGate);
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "visible") syncGate();
|
||||
});
|
||||
void tryLockPortrait();
|
||||
}
|
||||
@@ -33,6 +33,39 @@ body {
|
||||
var(--bg);
|
||||
}
|
||||
|
||||
.portrait-lock-gate {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: rgba(11, 14, 17, 0.96);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.portrait-lock-gate.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.portrait-lock-card {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.portrait-lock-title {
|
||||
margin: 0 0 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.portrait-lock-sub {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font: inherit;
|
||||
|
||||
Reference in New Issue
Block a user