Rebuild redmini as offline mirror of the existing H5 (same globals.css classes).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bf7167c185
commit
dbeac128bb
@ -1,36 +1,26 @@
|
|||||||
# redmini · nomadro 小红书离线镜像(1:1 产品面)
|
# redmini · nomadro 小红书离线包 = 官网 H5 镜像
|
||||||
|
|
||||||
小红书 **miniTool 无法联网**。本目录是 [nomadweb.nomadro.com](https://nomadweb.nomadro.com) 的离线内容镜像:
|
**不再另起 UI。** 样式直接来自 `frontend/src/app/globals.css`(打包为 `nomadro.css`),DOM class 与 `localhost:3000` 首页一致:`navbar` / `hero` / `dest-card` / `visa-card` / `journey-strip` / `footer` …
|
||||||
|
|
||||||
- 三环导航:探索 / 连接 / 成长(与官网一致)
|
数据为本机 mock 快照(无法联网)。完整登录/支付/RSVP 请用网页版。
|
||||||
- 首页:Hero、统计、热门目的地、游民一天、游民心声
|
|
||||||
- 探索:目的地列表/详情、地图快照、签证、智能匹配、城市对比
|
|
||||||
- 连接:活动、社区讨论、心声
|
|
||||||
- 成长:游民学院课时、博客全文、工具箱、费用估算、FAQ
|
|
||||||
- 我的:收藏、最近浏览、旅居计划清单(本地 Storage)
|
|
||||||
|
|
||||||
数据来自 `backend/app/data/mock_data.py` 等官网同源 mock(打包进 `src/data.js`)。
|
## 构建
|
||||||
|
|
||||||
## 打包上传
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd redmini
|
cd redmini
|
||||||
# 若 mock 有更新,先重建 data.js(在仓库根目录):
|
python scripts/build_data.py # 从 backend mock 导出 data.js
|
||||||
# cd ../backend && python -c "..." # 或复用 scripts 导出
|
# nomadro.css 由仓库维护:从 frontend globals 同步时再跑 sanitize
|
||||||
python scripts/assemble.py
|
python scripts/assemble.py
|
||||||
python scripts/static_scan.py # 禁止 fetch / XHR / WebSocket
|
python scripts/static_scan.py
|
||||||
python scripts/pack.py # -> dist/nomadro-redmini.zip
|
python scripts/pack.py # -> dist/nomadro-redmini.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
用小红书开发者工具上传 zip(根目录含 `index.html`)。
|
本地预览:打开 `release/index.html`(或 `tool/index.html`,需有 `tool/data.js`)。
|
||||||
|
|
||||||
## 目录
|
## 对照官网
|
||||||
|
|
||||||
| 路径 | 说明 |
|
| 官网 H5 | 离线包 |
|
||||||
|------|------|
|
|---------|--------|
|
||||||
| `src/data.js` | 离线数据包 |
|
| `frontend/src/app/globals.css` | `tool/nomadro.css` |
|
||||||
| `tool/` | HTML / CSS / JS / xhs-bridge |
|
| Hero / Destinations / Visa / FAQ | 同名 class 渲染 |
|
||||||
| `release/` | assemble 产物 |
|
| API JSON | `src/data.js` 内嵌 |
|
||||||
| `scripts/` | assemble / static_scan / pack |
|
|
||||||
|
|
||||||
本地浏览器预览:直接打开 `tool/index.html`(需先把 `src/data.js` 拷到 `tool/`,或跑 assemble 后打开 `release/index.html`)。
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -3,40 +3,49 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<title>nomadro · 数字游民</title>
|
<meta name="theme-color" content="#0a0e17" />
|
||||||
<link rel="stylesheet" href="./styles.css" />
|
<title>nomadro · 数字游民旅居指南</title>
|
||||||
|
<link rel="stylesheet" href="./nomadro.css" />
|
||||||
|
<style>
|
||||||
|
/* XHS offline shell — keep H5 look; hide web-only chrome that needs network */
|
||||||
|
.xhs-offline-banner {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: rgba(17, 24, 39, 0.95);
|
||||||
|
border-bottom: var(--border-glass);
|
||||||
|
}
|
||||||
|
.xhs-offline-banner strong { color: var(--accent-3); }
|
||||||
|
#app-root { min-height: 100vh; }
|
||||||
|
.page-panel { display: none; }
|
||||||
|
.page-panel.is-active { display: block; }
|
||||||
|
.xhs-back {
|
||||||
|
position: fixed;
|
||||||
|
left: 12px;
|
||||||
|
bottom: 18px;
|
||||||
|
z-index: 90;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: var(--border-glass);
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.xhs-back.is-on { display: block; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div class="xhs-offline-banner">
|
||||||
<header class="top-chrome" id="top-chrome">
|
<strong>离线镜像</strong> · 样式与结构同步官网 H5 · 数据为本机快照 · 完整互动请打开 nomadweb.nomadro.com
|
||||||
<div class="top-left">
|
|
||||||
<button type="button" class="icon-btn is-hidden" id="btn-back" aria-label="返回">←</button>
|
|
||||||
<div>
|
|
||||||
<p class="top-brand" id="top-brand">nomadro</p>
|
|
||||||
<p class="top-sub" id="top-sub">离线镜像</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="chip" id="btn-offline" title="离线说明">离线</button>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="main" id="main"></main>
|
|
||||||
|
|
||||||
<nav class="tabbar" id="tabbar" aria-label="主导航">
|
|
||||||
<button type="button" class="tab is-active" data-tab="home"><span>🏠</span><i>首页</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="explore"><span>🧭</span><i>探索</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="connect"><span>🤝</span><i>连接</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="grow"><span>🌱</span><i>成长</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="mine"><span>👤</span><i>我的</i></button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sheet is-hidden" id="sheet" role="dialog" aria-modal="true">
|
|
||||||
<div class="sheet-mask" id="sheet-mask"></div>
|
|
||||||
<div class="sheet-panel">
|
|
||||||
<div class="sheet-handle"></div>
|
|
||||||
<div id="sheet-body"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="app-root"></div>
|
||||||
|
<button type="button" class="xhs-back" id="xhs-back" aria-label="返回">←</button>
|
||||||
|
|
||||||
<script src="./data.js"></script>
|
<script src="./data.js"></script>
|
||||||
<script src="./xhs-bridge.js"></script>
|
<script src="./xhs-bridge.js"></script>
|
||||||
|
|||||||
12011
redmini/release/nomadro.css
Normal file
12011
redmini/release/nomadro.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,277 +0,0 @@
|
|||||||
:root {
|
|
||||||
--bg: #0b0a10;
|
|
||||||
--bg2: #14121c;
|
|
||||||
--card: rgba(255, 255, 255, 0.06);
|
|
||||||
--card2: rgba(255, 255, 255, 0.09);
|
|
||||||
--text: #f4f0ea;
|
|
||||||
--muted: #9a94a8;
|
|
||||||
--accent: #f0c674;
|
|
||||||
--accent2: #7dd3c7;
|
|
||||||
--danger: #f07178;
|
|
||||||
--border: rgba(255, 255, 255, 0.1);
|
|
||||||
--safe-b: env(safe-area-inset-bottom, 0px);
|
|
||||||
--safe-t: env(safe-area-inset-top, 0px);
|
|
||||||
--tab-h: 64px;
|
|
||||||
--top-h: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
button, input, select, textarea { font: inherit; color: inherit; }
|
|
||||||
button { border: 0; background: none; cursor: pointer; }
|
|
||||||
.is-hidden { display: none !important; }
|
|
||||||
|
|
||||||
#app {
|
|
||||||
min-height: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
padding-top: calc(var(--top-h) + var(--safe-t));
|
|
||||||
padding-bottom: calc(var(--tab-h) + var(--safe-b) + 8px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-chrome {
|
|
||||||
position: fixed; top: 0; left: 0; right: 0; z-index: 40;
|
|
||||||
height: calc(var(--top-h) + var(--safe-t));
|
|
||||||
padding: var(--safe-t) 14px 0;
|
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
|
||||||
background: rgba(11, 10, 16, 0.92);
|
|
||||||
backdrop-filter: blur(14px);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.top-left { display: flex; align-items: center; gap: 8px; }
|
|
||||||
.top-brand { font-weight: 700; font-size: 16px; letter-spacing: 0.4px; }
|
|
||||||
.top-sub { font-size: 11px; color: var(--muted); margin-top: 1px; }
|
|
||||||
.icon-btn {
|
|
||||||
width: 34px; height: 34px; border-radius: 10px;
|
|
||||||
background: var(--card); color: var(--text); font-size: 16px;
|
|
||||||
}
|
|
||||||
.chip {
|
|
||||||
padding: 6px 10px; border-radius: 999px; font-size: 12px;
|
|
||||||
background: rgba(240, 198, 116, 0.15); color: var(--accent);
|
|
||||||
border: 1px solid rgba(240, 198, 116, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main { padding: 12px 14px 20px; max-width: 720px; margin: 0 auto; }
|
|
||||||
|
|
||||||
.tabbar {
|
|
||||||
position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
|
|
||||||
height: calc(var(--tab-h) + var(--safe-b));
|
|
||||||
padding: 6px 6px var(--safe-b);
|
|
||||||
display: flex; background: rgba(16, 14, 24, 0.96);
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
backdrop-filter: blur(14px);
|
|
||||||
}
|
|
||||||
.tab {
|
|
||||||
flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
|
|
||||||
color: var(--muted); font-size: 10px; padding: 6px 0;
|
|
||||||
}
|
|
||||||
.tab span { font-size: 18px; line-height: 1; }
|
|
||||||
.tab.is-active { color: var(--accent); }
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
position: relative; overflow: hidden; border-radius: 22px;
|
|
||||||
padding: 22px 18px 20px;
|
|
||||||
background:
|
|
||||||
radial-gradient(500px 220px at 0% 0%, rgba(240, 198, 116, 0.22), transparent 55%),
|
|
||||||
radial-gradient(420px 200px at 100% 0%, rgba(125, 211, 199, 0.16), transparent 50%),
|
|
||||||
linear-gradient(165deg, #15121d, #0e0c14 70%);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.hero-badge {
|
|
||||||
display: inline-block; font-size: 11px; color: var(--accent2);
|
|
||||||
background: rgba(125, 211, 199, 0.12); padding: 4px 8px; border-radius: 999px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.hero h1 { font-size: 28px; line-height: 1.2; font-weight: 760; margin-bottom: 8px; }
|
|
||||||
.hero p { color: var(--muted); font-size: 13px; line-height: 1.55; }
|
|
||||||
.hero-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
|
|
||||||
.btn {
|
|
||||||
display: inline-flex; align-items: center; justify-content: center;
|
|
||||||
padding: 10px 14px; border-radius: 999px; font-size: 13px; font-weight: 600;
|
|
||||||
background: var(--accent); color: #1a1520;
|
|
||||||
}
|
|
||||||
.btn.ghost {
|
|
||||||
background: transparent; color: var(--text);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.btn.sm { padding: 7px 11px; font-size: 12px; }
|
|
||||||
|
|
||||||
.section { margin: 18px 0 8px; }
|
|
||||||
.section-head {
|
|
||||||
display: flex; align-items: baseline; justify-content: space-between;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.section-head h2 { font-size: 16px; font-weight: 700; }
|
|
||||||
.section-head .more { font-size: 12px; color: var(--accent); }
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 14px 0;
|
|
||||||
}
|
|
||||||
.stat {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 14px;
|
|
||||||
padding: 10px 6px; text-align: center;
|
|
||||||
}
|
|
||||||
.stat b { display: block; color: var(--accent); font-size: 16px; }
|
|
||||||
.stat span { display: block; margin-top: 3px; color: var(--muted); font-size: 10px; }
|
|
||||||
|
|
||||||
.ring-grid, .card-grid { display: grid; gap: 10px; }
|
|
||||||
.ring-grid { grid-template-columns: 1fr 1fr; }
|
|
||||||
.ring-card, .card {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 14px; text-align: left;
|
|
||||||
}
|
|
||||||
.ring-card .emoji, .card .emoji { font-size: 22px; display: block; margin-bottom: 8px; }
|
|
||||||
.ring-card strong, .card strong { display: block; font-size: 14px; margin-bottom: 4px; }
|
|
||||||
.ring-card span, .card .desc, .meta {
|
|
||||||
display: block; color: var(--muted); font-size: 12px; line-height: 1.45;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list { display: flex; flex-direction: column; gap: 10px; }
|
|
||||||
.list-item {
|
|
||||||
display: flex; gap: 12px; align-items: flex-start;
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 12px 14px; text-align: left; width: 100%;
|
|
||||||
}
|
|
||||||
.list-item .lead {
|
|
||||||
width: 42px; height: 42px; border-radius: 12px; flex-shrink: 0;
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
background: var(--card2); font-size: 22px;
|
|
||||||
}
|
|
||||||
.list-item .body { flex: 1; min-width: 0; }
|
|
||||||
.list-item .title { font-size: 14px; font-weight: 650; }
|
|
||||||
.list-item .meta { margin-top: 4px; }
|
|
||||||
.fav-dot { color: var(--accent); font-size: 12px; margin-left: 6px; }
|
|
||||||
|
|
||||||
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
|
|
||||||
.chip-btn {
|
|
||||||
padding: 7px 12px; border-radius: 999px; font-size: 12px;
|
|
||||||
background: var(--card); border: 1px solid var(--border); color: var(--muted);
|
|
||||||
}
|
|
||||||
.chip-btn.is-on { color: #1a1520; background: var(--accent); border-color: var(--accent); }
|
|
||||||
|
|
||||||
.detail-hero {
|
|
||||||
border-radius: 20px; padding: 18px; margin-bottom: 14px;
|
|
||||||
background: linear-gradient(160deg, rgba(240,198,116,.18), transparent 55%), var(--bg2);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.detail-hero .emoji { font-size: 36px; }
|
|
||||||
.detail-hero h1 { font-size: 24px; margin: 8px 0 4px; }
|
|
||||||
.kv {
|
|
||||||
display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 12px 0;
|
|
||||||
}
|
|
||||||
.kv .cell {
|
|
||||||
background: var(--card); border-radius: 14px; padding: 12px; border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.kv .k { color: var(--muted); font-size: 11px; }
|
|
||||||
.kv .v { display: block; margin-top: 4px; font-size: 16px; font-weight: 700; color: var(--accent); }
|
|
||||||
|
|
||||||
.hl { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 14px; }
|
|
||||||
.hl span {
|
|
||||||
font-size: 12px; padding: 6px 10px; border-radius: 999px;
|
|
||||||
background: rgba(125, 211, 199, 0.1); color: #b7efe6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 16px; line-height: 1.7; font-size: 14px; color: #e8e2f4;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
.article h2, .article h3 { color: var(--text); margin: 14px 0 8px; font-size: 16px; }
|
|
||||||
.article p { margin: 0 0 10px; }
|
|
||||||
.article ul, .article ol { padding-left: 18px; margin-bottom: 10px; }
|
|
||||||
.article li { margin: 4px 0; }
|
|
||||||
.article table { width: 100%; border-collapse: collapse; font-size: 12px; margin: 10px 0; }
|
|
||||||
.article th, .article td { border: 1px solid var(--border); padding: 6px 8px; text-align: left; }
|
|
||||||
|
|
||||||
.form {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 14px;
|
|
||||||
}
|
|
||||||
.field { margin-bottom: 12px; }
|
|
||||||
.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
|
|
||||||
.field input, .field select, .field textarea {
|
|
||||||
width: 100%; padding: 10px 12px; border-radius: 12px;
|
|
||||||
background: rgba(0,0,0,.25); border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.check-list { display: flex; flex-direction: column; gap: 8px; }
|
|
||||||
.check-item {
|
|
||||||
display: flex; align-items: center; gap: 10px;
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 14px;
|
|
||||||
padding: 12px 14px; text-align: left; width: 100%;
|
|
||||||
}
|
|
||||||
.check-item.is-on { border-color: rgba(240,198,116,.5); background: rgba(240,198,116,.08); }
|
|
||||||
.check-box {
|
|
||||||
width: 20px; height: 20px; border-radius: 6px; border: 1.5px solid var(--muted);
|
|
||||||
display: flex; align-items: center; justify-content: center; font-size: 12px;
|
|
||||||
}
|
|
||||||
.check-item.is-on .check-box { background: var(--accent); border-color: var(--accent); color: #1a1520; }
|
|
||||||
|
|
||||||
.lifestyle {
|
|
||||||
display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; -webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
.life-card {
|
|
||||||
min-width: 140px; flex-shrink: 0;
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 12px; text-align: left;
|
|
||||||
}
|
|
||||||
.life-card.is-on { border-color: var(--accent); }
|
|
||||||
.life-detail {
|
|
||||||
margin-top: 10px; padding: 12px; border-radius: 14px;
|
|
||||||
background: rgba(240,198,116,.08); color: #e8dfc8; font-size: 13px; line-height: 1.55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
margin: 10px 0 14px; padding: 10px 12px; border-radius: 12px;
|
|
||||||
background: rgba(240,198,116,.1); color: #e8d7a8; font-size: 12px; line-height: 1.5;
|
|
||||||
}
|
|
||||||
.empty { text-align: center; color: var(--muted); padding: 28px 10px; font-size: 13px; }
|
|
||||||
|
|
||||||
.map-board {
|
|
||||||
position: relative; height: 220px; border-radius: 18px; overflow: hidden;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 20% 40%, rgba(125,211,199,.15), transparent 30%),
|
|
||||||
radial-gradient(circle at 70% 35%, rgba(240,198,116,.12), transparent 28%),
|
|
||||||
linear-gradient(180deg, #17141f, #0f0d15);
|
|
||||||
border: 1px solid var(--border); margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.map-pin {
|
|
||||||
position: absolute; transform: translate(-50%, -100%);
|
|
||||||
font-size: 18px; filter: drop-shadow(0 2px 4px rgba(0,0,0,.5));
|
|
||||||
}
|
|
||||||
.map-pin span {
|
|
||||||
display: block; margin-top: 2px; font-size: 9px; color: var(--text);
|
|
||||||
background: rgba(0,0,0,.45); padding: 1px 4px; border-radius: 4px; white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sheet { position: fixed; inset: 0; z-index: 60; }
|
|
||||||
.sheet-mask { position: absolute; inset: 0; background: rgba(0,0,0,.55); }
|
|
||||||
.sheet-panel {
|
|
||||||
position: absolute; left: 0; right: 0; bottom: 0;
|
|
||||||
max-height: 78%; overflow: auto;
|
|
||||||
background: #16131e; border-radius: 20px 20px 0 0;
|
|
||||||
padding: 10px 16px calc(20px + var(--safe-b));
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.sheet-handle {
|
|
||||||
width: 42px; height: 4px; border-radius: 99px; background: rgba(255,255,255,.2);
|
|
||||||
margin: 4px auto 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compare-pick { display: flex; gap: 8px; margin-bottom: 10px; }
|
|
||||||
.compare-pick select { flex: 1; padding: 10px; border-radius: 12px; background: rgba(0,0,0,.25); border: 1px solid var(--border); }
|
|
||||||
.compare-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
||||||
.compare-table th, .compare-table td { border-bottom: 1px solid var(--border); padding: 10px 6px; text-align: left; }
|
|
||||||
.compare-table th { color: var(--muted); font-weight: 500; }
|
|
||||||
|
|
||||||
.quote {
|
|
||||||
background: var(--card); border-left: 3px solid var(--accent);
|
|
||||||
border-radius: 0 14px 14px 0; padding: 12px 14px; margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.quote p { font-size: 13px; line-height: 1.55; }
|
|
||||||
.quote .who { margin-top: 8px; color: var(--muted); font-size: 12px; }
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Copy tool/ into release/ and embed src/data.js (no network)."""
|
"""Copy tool/ + src/data.js into release/ for XHS zip (H5 mirror)."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -9,14 +10,30 @@ TOOL = ROOT / "tool"
|
|||||||
SRC_DATA = ROOT / "src" / "data.js"
|
SRC_DATA = ROOT / "src" / "data.js"
|
||||||
RELEASE = ROOT / "release"
|
RELEASE = ROOT / "release"
|
||||||
|
|
||||||
|
FILES = (
|
||||||
|
"index.html",
|
||||||
|
"app.js",
|
||||||
|
"nomadro.css",
|
||||||
|
"xhs-bridge.js",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
if RELEASE.exists():
|
if RELEASE.exists():
|
||||||
shutil.rmtree(RELEASE)
|
shutil.rmtree(RELEASE)
|
||||||
RELEASE.mkdir(parents=True)
|
RELEASE.mkdir(parents=True)
|
||||||
for name in ("index.html", "styles.css", "app.js", "xhs-bridge.js"):
|
for name in FILES:
|
||||||
shutil.copy2(TOOL / name, RELEASE / name)
|
src = TOOL / name
|
||||||
|
if not src.exists():
|
||||||
|
print(f"missing {src}")
|
||||||
|
return 1
|
||||||
|
shutil.copy2(src, RELEASE / name)
|
||||||
|
if not SRC_DATA.exists():
|
||||||
|
print(f"missing {SRC_DATA} — run: python scripts/build_data.py")
|
||||||
|
return 1
|
||||||
shutil.copy2(SRC_DATA, RELEASE / "data.js")
|
shutil.copy2(SRC_DATA, RELEASE / "data.js")
|
||||||
|
# keep a copy in tool/ for local preview
|
||||||
|
shutil.copy2(SRC_DATA, TOOL / "data.js")
|
||||||
print(f"assembled {RELEASE}")
|
print(f"assembled {RELEASE}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@ -34,13 +34,13 @@ def main() -> int:
|
|||||||
"meetups": len(meetups),
|
"meetups": len(meetups),
|
||||||
"blog": len(m.BLOG_POSTS),
|
"blog": len(m.BLOG_POSTS),
|
||||||
"faqs": len(m.FAQS),
|
"faqs": len(m.FAQS),
|
||||||
|
"total_nomads": "35.6M",
|
||||||
|
"countries": 195,
|
||||||
|
"satisfaction": 87,
|
||||||
|
"avg_cost": sum(d["cost"] for d in m.DESTINATIONS) // max(1, len(m.DESTINATIONS)) // 100,
|
||||||
|
"active_today": 127,
|
||||||
},
|
},
|
||||||
"ticker": [
|
"ticker": m.TICKER_MESSAGES,
|
||||||
"🌏 全球数字游民正在路上",
|
|
||||||
"📋 签证政策实时整理(离线快照)",
|
|
||||||
"🎉 同城活动与社区讨论可先浏览",
|
|
||||||
"🎓 游民学院免费课时可学",
|
|
||||||
],
|
|
||||||
"rings": {
|
"rings": {
|
||||||
"explore": [
|
"explore": [
|
||||||
{"id": "destinations", "emoji": "🌴", "title": "目的地", "desc": "发现下一座城市"},
|
{"id": "destinations", "emoji": "🌴", "title": "目的地", "desc": "发现下一座城市"},
|
||||||
|
|||||||
54
redmini/scripts/sync_h5_css.py
Normal file
54
redmini/scripts/sync_h5_css.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Sync frontend/src/app/globals.css -> redmini/tool/nomadro.css (Chrome 61 sanitize)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
SRC = ROOT / "frontend" / "src" / "app" / "globals.css"
|
||||||
|
OUT = ROOT / "redmini" / "tool" / "nomadro.css"
|
||||||
|
|
||||||
|
|
||||||
|
def unwrap_layers(text: str) -> str:
|
||||||
|
while True:
|
||||||
|
m = re.search(r"@layer\s+[\w-]+\s*\{", text)
|
||||||
|
if not m:
|
||||||
|
break
|
||||||
|
start = m.start()
|
||||||
|
i = m.end()
|
||||||
|
depth = 1
|
||||||
|
while i < len(text) and depth:
|
||||||
|
if text[i] == "{":
|
||||||
|
depth += 1
|
||||||
|
elif text[i] == "}":
|
||||||
|
depth -= 1
|
||||||
|
i += 1
|
||||||
|
text = text[:start] + text[m.end() : i - 1] + text[i:]
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
src = SRC.read_text(encoding="utf-8")
|
||||||
|
src = re.sub(
|
||||||
|
r"html:has\(\[data-ebook-root\]\)[^{]*\{(?:[^{}]|\{[^{}]*\})*\}",
|
||||||
|
"",
|
||||||
|
src,
|
||||||
|
)
|
||||||
|
src = re.sub(
|
||||||
|
r"html:has\(\[data-ebook-root\]\)\s+body\s*\{(?:[^{}]|\{[^{}]*\})*\}",
|
||||||
|
"",
|
||||||
|
src,
|
||||||
|
)
|
||||||
|
src = unwrap_layers(src)
|
||||||
|
src = src.replace("var(--font-outfit), ", "").replace(", var(--font-outfit)", "")
|
||||||
|
OUT.write_text(
|
||||||
|
"/* Mirror of frontend/src/app/globals.css — synced for XHS offline H5 */\n" + src,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
print(f"wrote {OUT} ({OUT.stat().st_size} bytes) open={src.count('{')} close={src.count('}')}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
File diff suppressed because one or more lines are too long
1597
redmini/tool/app.js
1597
redmini/tool/app.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -3,40 +3,49 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<title>nomadro · 数字游民</title>
|
<meta name="theme-color" content="#0a0e17" />
|
||||||
<link rel="stylesheet" href="./styles.css" />
|
<title>nomadro · 数字游民旅居指南</title>
|
||||||
|
<link rel="stylesheet" href="./nomadro.css" />
|
||||||
|
<style>
|
||||||
|
/* XHS offline shell — keep H5 look; hide web-only chrome that needs network */
|
||||||
|
.xhs-offline-banner {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: rgba(17, 24, 39, 0.95);
|
||||||
|
border-bottom: var(--border-glass);
|
||||||
|
}
|
||||||
|
.xhs-offline-banner strong { color: var(--accent-3); }
|
||||||
|
#app-root { min-height: 100vh; }
|
||||||
|
.page-panel { display: none; }
|
||||||
|
.page-panel.is-active { display: block; }
|
||||||
|
.xhs-back {
|
||||||
|
position: fixed;
|
||||||
|
left: 12px;
|
||||||
|
bottom: 18px;
|
||||||
|
z-index: 90;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: var(--border-glass);
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.xhs-back.is-on { display: block; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div class="xhs-offline-banner">
|
||||||
<header class="top-chrome" id="top-chrome">
|
<strong>离线镜像</strong> · 样式与结构同步官网 H5 · 数据为本机快照 · 完整互动请打开 nomadweb.nomadro.com
|
||||||
<div class="top-left">
|
|
||||||
<button type="button" class="icon-btn is-hidden" id="btn-back" aria-label="返回">←</button>
|
|
||||||
<div>
|
|
||||||
<p class="top-brand" id="top-brand">nomadro</p>
|
|
||||||
<p class="top-sub" id="top-sub">离线镜像</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="chip" id="btn-offline" title="离线说明">离线</button>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="main" id="main"></main>
|
|
||||||
|
|
||||||
<nav class="tabbar" id="tabbar" aria-label="主导航">
|
|
||||||
<button type="button" class="tab is-active" data-tab="home"><span>🏠</span><i>首页</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="explore"><span>🧭</span><i>探索</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="connect"><span>🤝</span><i>连接</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="grow"><span>🌱</span><i>成长</i></button>
|
|
||||||
<button type="button" class="tab" data-tab="mine"><span>👤</span><i>我的</i></button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sheet is-hidden" id="sheet" role="dialog" aria-modal="true">
|
|
||||||
<div class="sheet-mask" id="sheet-mask"></div>
|
|
||||||
<div class="sheet-panel">
|
|
||||||
<div class="sheet-handle"></div>
|
|
||||||
<div id="sheet-body"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="app-root"></div>
|
||||||
|
<button type="button" class="xhs-back" id="xhs-back" aria-label="返回">←</button>
|
||||||
|
|
||||||
<script src="./data.js"></script>
|
<script src="./data.js"></script>
|
||||||
<script src="./xhs-bridge.js"></script>
|
<script src="./xhs-bridge.js"></script>
|
||||||
|
|||||||
12011
redmini/tool/nomadro.css
Normal file
12011
redmini/tool/nomadro.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,277 +0,0 @@
|
|||||||
:root {
|
|
||||||
--bg: #0b0a10;
|
|
||||||
--bg2: #14121c;
|
|
||||||
--card: rgba(255, 255, 255, 0.06);
|
|
||||||
--card2: rgba(255, 255, 255, 0.09);
|
|
||||||
--text: #f4f0ea;
|
|
||||||
--muted: #9a94a8;
|
|
||||||
--accent: #f0c674;
|
|
||||||
--accent2: #7dd3c7;
|
|
||||||
--danger: #f07178;
|
|
||||||
--border: rgba(255, 255, 255, 0.1);
|
|
||||||
--safe-b: env(safe-area-inset-bottom, 0px);
|
|
||||||
--safe-t: env(safe-area-inset-top, 0px);
|
|
||||||
--tab-h: 64px;
|
|
||||||
--top-h: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
button, input, select, textarea { font: inherit; color: inherit; }
|
|
||||||
button { border: 0; background: none; cursor: pointer; }
|
|
||||||
.is-hidden { display: none !important; }
|
|
||||||
|
|
||||||
#app {
|
|
||||||
min-height: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
padding-top: calc(var(--top-h) + var(--safe-t));
|
|
||||||
padding-bottom: calc(var(--tab-h) + var(--safe-b) + 8px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-chrome {
|
|
||||||
position: fixed; top: 0; left: 0; right: 0; z-index: 40;
|
|
||||||
height: calc(var(--top-h) + var(--safe-t));
|
|
||||||
padding: var(--safe-t) 14px 0;
|
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
|
||||||
background: rgba(11, 10, 16, 0.92);
|
|
||||||
backdrop-filter: blur(14px);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.top-left { display: flex; align-items: center; gap: 8px; }
|
|
||||||
.top-brand { font-weight: 700; font-size: 16px; letter-spacing: 0.4px; }
|
|
||||||
.top-sub { font-size: 11px; color: var(--muted); margin-top: 1px; }
|
|
||||||
.icon-btn {
|
|
||||||
width: 34px; height: 34px; border-radius: 10px;
|
|
||||||
background: var(--card); color: var(--text); font-size: 16px;
|
|
||||||
}
|
|
||||||
.chip {
|
|
||||||
padding: 6px 10px; border-radius: 999px; font-size: 12px;
|
|
||||||
background: rgba(240, 198, 116, 0.15); color: var(--accent);
|
|
||||||
border: 1px solid rgba(240, 198, 116, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main { padding: 12px 14px 20px; max-width: 720px; margin: 0 auto; }
|
|
||||||
|
|
||||||
.tabbar {
|
|
||||||
position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
|
|
||||||
height: calc(var(--tab-h) + var(--safe-b));
|
|
||||||
padding: 6px 6px var(--safe-b);
|
|
||||||
display: flex; background: rgba(16, 14, 24, 0.96);
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
backdrop-filter: blur(14px);
|
|
||||||
}
|
|
||||||
.tab {
|
|
||||||
flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
|
|
||||||
color: var(--muted); font-size: 10px; padding: 6px 0;
|
|
||||||
}
|
|
||||||
.tab span { font-size: 18px; line-height: 1; }
|
|
||||||
.tab.is-active { color: var(--accent); }
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
position: relative; overflow: hidden; border-radius: 22px;
|
|
||||||
padding: 22px 18px 20px;
|
|
||||||
background:
|
|
||||||
radial-gradient(500px 220px at 0% 0%, rgba(240, 198, 116, 0.22), transparent 55%),
|
|
||||||
radial-gradient(420px 200px at 100% 0%, rgba(125, 211, 199, 0.16), transparent 50%),
|
|
||||||
linear-gradient(165deg, #15121d, #0e0c14 70%);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.hero-badge {
|
|
||||||
display: inline-block; font-size: 11px; color: var(--accent2);
|
|
||||||
background: rgba(125, 211, 199, 0.12); padding: 4px 8px; border-radius: 999px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.hero h1 { font-size: 28px; line-height: 1.2; font-weight: 760; margin-bottom: 8px; }
|
|
||||||
.hero p { color: var(--muted); font-size: 13px; line-height: 1.55; }
|
|
||||||
.hero-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
|
|
||||||
.btn {
|
|
||||||
display: inline-flex; align-items: center; justify-content: center;
|
|
||||||
padding: 10px 14px; border-radius: 999px; font-size: 13px; font-weight: 600;
|
|
||||||
background: var(--accent); color: #1a1520;
|
|
||||||
}
|
|
||||||
.btn.ghost {
|
|
||||||
background: transparent; color: var(--text);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.btn.sm { padding: 7px 11px; font-size: 12px; }
|
|
||||||
|
|
||||||
.section { margin: 18px 0 8px; }
|
|
||||||
.section-head {
|
|
||||||
display: flex; align-items: baseline; justify-content: space-between;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.section-head h2 { font-size: 16px; font-weight: 700; }
|
|
||||||
.section-head .more { font-size: 12px; color: var(--accent); }
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 14px 0;
|
|
||||||
}
|
|
||||||
.stat {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 14px;
|
|
||||||
padding: 10px 6px; text-align: center;
|
|
||||||
}
|
|
||||||
.stat b { display: block; color: var(--accent); font-size: 16px; }
|
|
||||||
.stat span { display: block; margin-top: 3px; color: var(--muted); font-size: 10px; }
|
|
||||||
|
|
||||||
.ring-grid, .card-grid { display: grid; gap: 10px; }
|
|
||||||
.ring-grid { grid-template-columns: 1fr 1fr; }
|
|
||||||
.ring-card, .card {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 14px; text-align: left;
|
|
||||||
}
|
|
||||||
.ring-card .emoji, .card .emoji { font-size: 22px; display: block; margin-bottom: 8px; }
|
|
||||||
.ring-card strong, .card strong { display: block; font-size: 14px; margin-bottom: 4px; }
|
|
||||||
.ring-card span, .card .desc, .meta {
|
|
||||||
display: block; color: var(--muted); font-size: 12px; line-height: 1.45;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list { display: flex; flex-direction: column; gap: 10px; }
|
|
||||||
.list-item {
|
|
||||||
display: flex; gap: 12px; align-items: flex-start;
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 12px 14px; text-align: left; width: 100%;
|
|
||||||
}
|
|
||||||
.list-item .lead {
|
|
||||||
width: 42px; height: 42px; border-radius: 12px; flex-shrink: 0;
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
background: var(--card2); font-size: 22px;
|
|
||||||
}
|
|
||||||
.list-item .body { flex: 1; min-width: 0; }
|
|
||||||
.list-item .title { font-size: 14px; font-weight: 650; }
|
|
||||||
.list-item .meta { margin-top: 4px; }
|
|
||||||
.fav-dot { color: var(--accent); font-size: 12px; margin-left: 6px; }
|
|
||||||
|
|
||||||
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
|
|
||||||
.chip-btn {
|
|
||||||
padding: 7px 12px; border-radius: 999px; font-size: 12px;
|
|
||||||
background: var(--card); border: 1px solid var(--border); color: var(--muted);
|
|
||||||
}
|
|
||||||
.chip-btn.is-on { color: #1a1520; background: var(--accent); border-color: var(--accent); }
|
|
||||||
|
|
||||||
.detail-hero {
|
|
||||||
border-radius: 20px; padding: 18px; margin-bottom: 14px;
|
|
||||||
background: linear-gradient(160deg, rgba(240,198,116,.18), transparent 55%), var(--bg2);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.detail-hero .emoji { font-size: 36px; }
|
|
||||||
.detail-hero h1 { font-size: 24px; margin: 8px 0 4px; }
|
|
||||||
.kv {
|
|
||||||
display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 12px 0;
|
|
||||||
}
|
|
||||||
.kv .cell {
|
|
||||||
background: var(--card); border-radius: 14px; padding: 12px; border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.kv .k { color: var(--muted); font-size: 11px; }
|
|
||||||
.kv .v { display: block; margin-top: 4px; font-size: 16px; font-weight: 700; color: var(--accent); }
|
|
||||||
|
|
||||||
.hl { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 14px; }
|
|
||||||
.hl span {
|
|
||||||
font-size: 12px; padding: 6px 10px; border-radius: 999px;
|
|
||||||
background: rgba(125, 211, 199, 0.1); color: #b7efe6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 16px; line-height: 1.7; font-size: 14px; color: #e8e2f4;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
.article h2, .article h3 { color: var(--text); margin: 14px 0 8px; font-size: 16px; }
|
|
||||||
.article p { margin: 0 0 10px; }
|
|
||||||
.article ul, .article ol { padding-left: 18px; margin-bottom: 10px; }
|
|
||||||
.article li { margin: 4px 0; }
|
|
||||||
.article table { width: 100%; border-collapse: collapse; font-size: 12px; margin: 10px 0; }
|
|
||||||
.article th, .article td { border: 1px solid var(--border); padding: 6px 8px; text-align: left; }
|
|
||||||
|
|
||||||
.form {
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 14px;
|
|
||||||
}
|
|
||||||
.field { margin-bottom: 12px; }
|
|
||||||
.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
|
|
||||||
.field input, .field select, .field textarea {
|
|
||||||
width: 100%; padding: 10px 12px; border-radius: 12px;
|
|
||||||
background: rgba(0,0,0,.25); border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.check-list { display: flex; flex-direction: column; gap: 8px; }
|
|
||||||
.check-item {
|
|
||||||
display: flex; align-items: center; gap: 10px;
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 14px;
|
|
||||||
padding: 12px 14px; text-align: left; width: 100%;
|
|
||||||
}
|
|
||||||
.check-item.is-on { border-color: rgba(240,198,116,.5); background: rgba(240,198,116,.08); }
|
|
||||||
.check-box {
|
|
||||||
width: 20px; height: 20px; border-radius: 6px; border: 1.5px solid var(--muted);
|
|
||||||
display: flex; align-items: center; justify-content: center; font-size: 12px;
|
|
||||||
}
|
|
||||||
.check-item.is-on .check-box { background: var(--accent); border-color: var(--accent); color: #1a1520; }
|
|
||||||
|
|
||||||
.lifestyle {
|
|
||||||
display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; -webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
.life-card {
|
|
||||||
min-width: 140px; flex-shrink: 0;
|
|
||||||
background: var(--card); border: 1px solid var(--border); border-radius: 16px;
|
|
||||||
padding: 12px; text-align: left;
|
|
||||||
}
|
|
||||||
.life-card.is-on { border-color: var(--accent); }
|
|
||||||
.life-detail {
|
|
||||||
margin-top: 10px; padding: 12px; border-radius: 14px;
|
|
||||||
background: rgba(240,198,116,.08); color: #e8dfc8; font-size: 13px; line-height: 1.55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
|
||||||
margin: 10px 0 14px; padding: 10px 12px; border-radius: 12px;
|
|
||||||
background: rgba(240,198,116,.1); color: #e8d7a8; font-size: 12px; line-height: 1.5;
|
|
||||||
}
|
|
||||||
.empty { text-align: center; color: var(--muted); padding: 28px 10px; font-size: 13px; }
|
|
||||||
|
|
||||||
.map-board {
|
|
||||||
position: relative; height: 220px; border-radius: 18px; overflow: hidden;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 20% 40%, rgba(125,211,199,.15), transparent 30%),
|
|
||||||
radial-gradient(circle at 70% 35%, rgba(240,198,116,.12), transparent 28%),
|
|
||||||
linear-gradient(180deg, #17141f, #0f0d15);
|
|
||||||
border: 1px solid var(--border); margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.map-pin {
|
|
||||||
position: absolute; transform: translate(-50%, -100%);
|
|
||||||
font-size: 18px; filter: drop-shadow(0 2px 4px rgba(0,0,0,.5));
|
|
||||||
}
|
|
||||||
.map-pin span {
|
|
||||||
display: block; margin-top: 2px; font-size: 9px; color: var(--text);
|
|
||||||
background: rgba(0,0,0,.45); padding: 1px 4px; border-radius: 4px; white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sheet { position: fixed; inset: 0; z-index: 60; }
|
|
||||||
.sheet-mask { position: absolute; inset: 0; background: rgba(0,0,0,.55); }
|
|
||||||
.sheet-panel {
|
|
||||||
position: absolute; left: 0; right: 0; bottom: 0;
|
|
||||||
max-height: 78%; overflow: auto;
|
|
||||||
background: #16131e; border-radius: 20px 20px 0 0;
|
|
||||||
padding: 10px 16px calc(20px + var(--safe-b));
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.sheet-handle {
|
|
||||||
width: 42px; height: 4px; border-radius: 99px; background: rgba(255,255,255,.2);
|
|
||||||
margin: 4px auto 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compare-pick { display: flex; gap: 8px; margin-bottom: 10px; }
|
|
||||||
.compare-pick select { flex: 1; padding: 10px; border-radius: 12px; background: rgba(0,0,0,.25); border: 1px solid var(--border); }
|
|
||||||
.compare-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
||||||
.compare-table th, .compare-table td { border-bottom: 1px solid var(--border); padding: 10px 6px; text-align: left; }
|
|
||||||
.compare-table th { color: var(--muted); font-weight: 500; }
|
|
||||||
|
|
||||||
.quote {
|
|
||||||
background: var(--card); border-left: 3px solid var(--accent);
|
|
||||||
border-radius: 0 14px 14px 0; padding: 12px 14px; margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.quote p { font-size: 13px; line-height: 1.55; }
|
|
||||||
.quote .who { margin-top: 8px; color: var(--muted); font-size: 12px; }
|
|
||||||
Loading…
Reference in New Issue
Block a user