Files
FamilyBook/assets/css/app.css
T
laoyang 1c460a0e83 图片可裁剪 · 名册按关系分组 · 个人设置独立 · 顶栏下拉菜单
① 上传图片一律可裁剪,比例由图位决定
   新增 app/ImageSlot.php 统一配置(头像 1:1/512×512;题图 16:9 只留注释,
   将来启用只加一行)。前端只挑区域、写回归一化矩形 "x,y,w,h",真正裁剪在服务端
   Upload::fitRegion():夹回图内 → 在框内居中收缩到目标比例(不外扩)→ 钉住 sx+sw ≤ w。
   比例解析单独写 parseRatioAttr():parseFloat('16:9') 得 16,会把题图裁成细长条且不报错。

② 名册按关系分组,顺序每人可自定
   分类全家共用(person_groups,另一个人可以自己加分类);「自己的」只体现在顺序上
   (user_settings.group_order,每人一份)。persons.group_id 故意不加外键(老库只能
   ADD COLUMN),完整性由 PersonGroup 保证:删分类把人置回未分类,人不会从名册消失。

③ 设置分两级:系统设置 / 个人设置
   个人设置拆成 templates/settings/me.php + _profile.php + _nicknames.php,
   取代 templates/people/me.php(旧路由与旧表单仍兼容)。

④ 右上角「姓名/头像 + 下拉菜单」
   原来是两个裸链接,窄屏会被 CSS 藏掉导致改密码/退出不可达;改成 <details> 零 JS 菜单
   (app.js 只补「点外面 / Esc」)。菜单项:个人设置 / 系统设置(管理员)/ 修改密码 / 退出登录。

Schema 版本 3 → 4;安装与老库升级共用 PersonGroup::backfillMissing() 一条回填路径。

验证:php -l 全量;e2e 202/0(新增「关系分组」与「图片裁剪」两节);老库升级 49/0;
桌面几何 108/0;移动端几何 192/0(360/375/390/414);交互 76/0;
反向验证 31/31(逐条把修复改回旧写法,指定的目标断言必须变红);
部署后本地↔线上 72 个文件 sha256 逐字节一致。
2026-09-30 11:06:22 +08:00

684 lines
29 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
家书 FamilyBook · 界面样式
配色取自图标:宣纸米 / 砖红封面 / 深褐书脊 / 纸白
原则:扁平、留白充足、字重克制(正文 400,标题 500-600)
============================================================ */
:root {
--paper: #F7F3EA;
--surface: #FFFFFF;
--ink: #2C231D;
--muted: #7E6B5A;
--faint: #A8988A;
--brand: #A84A28;
--brand-2: #C96F45;
--brand-ink:#6E3820;
--green: #6F9E5C;
--red: #C0533F;
--line: rgba(44, 35, 29, .10);
--line-2: rgba(44, 35, 29, .06);
--radius: 16px;
--radius-s: 10px;
--shadow: 0 1px 2px rgba(44, 35, 29, .05);
--shadow-l: 0 4px 16px rgba(44, 35, 29, .08);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font: 15px/1.7 -apple-system, BlinkMacSystemFont, "PingFang SC",
"Microsoft YaHei", "Hiragino Sans GB", sans-serif;
-webkit-font-smoothing: antialiased;
}
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
/* ---------------- 顶栏 ---------------- */
.topbar {
position: sticky; top: 0; z-index: 20;
background: var(--surface);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
max-width: 1080px; margin: 0 auto; padding: 0 24px;
height: 60px; display: flex; align-items: center; gap: 24px;
}
.brand { display: flex; align-items: center; gap: 10px; color: var(--ink); }
.brand:hover { text-decoration: none; }
.brand img { width: 30px; height: 30px; border-radius: 8px; display: block; }
.brand span { font-size: 16px; font-weight: 600; letter-spacing: .5px; }
.nav { display: flex; gap: 4px; margin-left: 8px; }
.nav a {
padding: 6px 12px; border-radius: 999px; color: var(--muted);
font-size: 14px;
}
.nav a:hover { background: var(--line-2); text-decoration: none; }
.nav a.on { background: var(--brand); color: #fff; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.who { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 14px; }
/* 姓名 + 头像是个下拉菜单的「把手」——hover 时给点反馈,让人知道它可点 */
.who-menu { position: relative; }
.who-menu > summary { list-style: none; cursor: pointer; padding: 4px 6px; border-radius: 999px; }
.who-menu > summary::-webkit-details-marker { display: none; }
.who-menu > summary:hover { background: var(--line-2); }
.who-menu > summary:hover .who-name { color: var(--brand); }
.who-menu[open] > summary { background: var(--line-2); }
.who-menu[open] > summary .caret { transform: rotate(180deg); }
.who img { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; }
.who img { transition: box-shadow .15s ease; }
.who-menu > summary:hover img { box-shadow: 0 0 0 2px var(--brand-2); }
/* 那个小三角:没有它,用户不知道"名字能点开" */
.caret {
width: 0; height: 0; margin-left: -2px;
border-left: 4px solid transparent; border-right: 4px solid transparent;
border-top: 5px solid var(--faint);
transition: transform .15s ease;
}
.menu-list {
position: absolute; right: 0; top: calc(100% + 8px); z-index: 30;
min-width: 148px; padding: 6px;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius-s); box-shadow: var(--shadow-l);
display: flex; flex-direction: column;
}
.menu-list a {
padding: 8px 12px; border-radius: 8px; color: var(--ink);
font-size: 14px; white-space: nowrap;
}
.menu-list a:hover { background: var(--line-2); text-decoration: none; color: var(--brand); }
/* 退出放在最下面并隔一条线:它是"离开",和上面几个"改点什么"不是一类动作 */
.menu-list .menu-exit {
margin-top: 4px; padding-top: 9px; border-top: 1px solid var(--line-2);
border-radius: 0 0 8px 8px; color: var(--muted);
}
.menu-list .menu-exit:hover { color: var(--brand); }
.link-quiet { color: var(--muted); font-size: 14px; }
.link-quiet:hover { color: var(--brand); }
/* 顶栏里的文字一律不许换行 —— 一换行就是「首/页」「名/册」竖排,很难看 */
.nav a, .topbar-right a, .who-name { white-space: nowrap; }
/* ---------------- 布局 ---------------- */
.container { max-width: 1080px; margin: 0 auto; padding: 28px 24px 64px; }
.container-narrow { max-width: 560px; margin: 0 auto; padding: 28px 24px 64px; }
.page-head { display: flex; align-items: flex-end; gap: 16px; margin-bottom: 20px; }
.page-head h1 { font-size: 22px; font-weight: 600; margin: 0; }
.page-head .sub { color: var(--muted); font-size: 14px; }
.page-head .actions { margin-left: auto; display: flex; gap: 8px; }
.section { margin-top: 28px; }
.section-title {
display: flex; align-items: center; gap: 10px;
font-size: 15px; font-weight: 600; margin: 0 0 12px;
}
.section-title .hint { font-weight: 400; color: var(--muted); font-size: 13px; }
/* ---------------- 卡片 ---------------- */
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.card-pad { padding: 20px; }
/* 首页头图 */
.hero {
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); box-shadow: var(--shadow);
padding: 26px 28px;
display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
}
.hero-logo { width: 64px; height: 64px; border-radius: 16px; flex: 0 0 auto; }
.hero-main { flex: 1 1 240px; min-width: 0; }
.hero h1 { font-size: 24px; font-weight: 600; margin: 0 0 4px; }
.hero .motto { color: var(--muted); margin: 0; font-size: 14px; }
/* 那句小字到底是谁的:自己写的那句要标一下,否则用户会以为这是系统统一发的 */
.motto-from {
display: inline-block; margin-left: 6px; padding: 0 7px;
border-radius: 999px; background: var(--line-2);
font-size: 11px; color: var(--faint);
}
.motto-edit { margin-left: 8px; font-size: 12px; color: var(--brand-2); white-space: nowrap; }
/* 详情页顶部引用本人的一句话 */
.motto-line {
margin: -8px 0 16px; padding-left: 12px;
border-left: 3px solid var(--brand-2);
color: var(--muted); font-size: 14px;
}
.hero-stats { margin-left: auto; display: flex; gap: 28px; }
.stat { text-align: center; }
.stat b { display: block; font-size: 24px; font-weight: 600; line-height: 1.2; }
.stat span { font-size: 12px; color: var(--muted); }
/* ---------------- 成员卡 ---------------- */
.people-grid {
display: grid; gap: 16px; margin-top: 4px;
grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
}
.person {
display: block; color: var(--ink);
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); overflow: hidden;
box-shadow: var(--shadow);
transition: transform .15s ease, box-shadow .15s ease;
}
.person:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.person .ph {
background: #F1E7D6; aspect-ratio: 1 / 1; overflow: hidden;
}
.person .ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.person .pb { padding: 12px 14px 14px; }
.person .pname { font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 6px; }
.person .pmeta { color: var(--muted); font-size: 13px; margin-top: 2px; }
.badge {
display: inline-block; padding: 1px 8px; border-radius: 999px;
font-size: 11px; font-weight: 500; line-height: 1.7;
background: var(--line-2); color: var(--muted);
}
.badge-admin { background: #F6E3D7; color: var(--brand-ink); }
.badge-open { background: #E6F0DC; color: #4A7226; }
.badge-me { background: var(--brand); color: #fff; }
/* 生日条 —— 一个人可能同时出两条(农历与阳历不在同一天),
落在同一天时并成一条、kinds 里挂多个标签,正好对应"要分别提醒"。 */
.bdays { display: flex; gap: 12px; flex-wrap: wrap; }
.bday {
display: flex; align-items: center; gap: 12px;
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 10px 18px 10px 10px;
box-shadow: var(--shadow);
color: var(--ink); text-decoration: none;
transition: transform .15s ease, box-shadow .15s ease;
}
.bday:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.bday img { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.bday b { font-weight: 500; }
.bday .d { color: var(--brand); font-size: 13px; }
.bday-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 3px; }
.bday-date { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
/* 「身份证生日 / 阳历生日 / 农历生日」的标签,首页与详情页共用 */
.bd-kind {
display: inline-block; padding: 0 7px; border-radius: 999px;
font-size: 11px; line-height: 1.8;
background: var(--line-2); color: var(--muted);
}
.bd-kind-id { background: #E6F0DC; color: #4A7226; }
.bd-kind-solar { background: #F6E3D7; color: var(--brand-ink); }
.bd-kind-lunar { background: #E8E4F2; color: #55477E; }
/* 详情页的三个生日 */
.bd-list { list-style: none; margin: 0; padding: 0; }
.bd-list li { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; flex-wrap: wrap; }
.bd-list .bd-val { font-variant-numeric: tabular-nums; }
/* ---------------- 表单 ---------------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field.full { grid-column: 1 / -1; }
.field label { font-size: 13px; color: var(--muted); font-weight: 500; }
.field .tip { font-size: 12px; color: var(--muted); }
.input, .select, textarea.input {
width: 100%; padding: 10px 12px; font: inherit; font-size: 14px;
color: var(--ink); background: #fff;
border: 1px solid var(--line); border-radius: var(--radius-s);
transition: border-color .15s ease, box-shadow .15s ease;
}
.input:focus, .select:focus, textarea.input:focus {
outline: none; border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(168, 74, 40, .12);
}
textarea.input { min-height: 76px; resize: vertical; }
.err { color: var(--red); font-size: 12px; }
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
padding: 9px 16px; font: inherit; font-size: 14px; font-weight: 500;
border-radius: var(--radius-s); border: 1px solid transparent; cursor: pointer;
background: var(--brand); color: #fff; transition: background .15s ease;
}
.btn:hover { background: #944022; text-decoration: none; }
.btn-ghost { background: #fff; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { background: #FAF7F1; }
.btn-danger { background: #fff; color: var(--red); border-color: rgba(192,83,63,.35); }
.btn-danger:hover { background: #FDF1EE; }
.btn-sm { padding: 5px 12px; font-size: 13px; }
.btn-row { display: flex; gap: 10px; margin-top: 20px; }
/* ---------------- 详情 ---------------- */
.detail { display: grid; grid-template-columns: 260px 1fr; gap: 24px; }
.detail-photo {
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--radius); padding: 16px; text-align: center;
box-shadow: var(--shadow);
}
.detail-photo img {
width: 100%; aspect-ratio: 1/1; object-fit: cover;
border-radius: 12px; display: block;
}
.detail-photo .name { font-size: 18px; font-weight: 600; margin: 14px 0 4px; }
.detail-photo .sub { color: var(--muted); font-size: 13px; }
.kv { display: flex; flex-direction: column; }
.kv-row {
display: grid; grid-template-columns: 96px 1fr; gap: 12px;
padding: 12px 0; border-bottom: 1px solid var(--line-2);
}
.kv-row:last-child { border-bottom: 0; }
.kv-row dt { color: var(--muted); font-size: 13px; }
.kv-row dd { margin: 0; font-size: 14px; word-break: break-all; }
.mono { font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; letter-spacing: .5px; }
/* ---------------- 提示 ---------------- */
.flash { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.flash-item {
padding: 10px 14px; border-radius: var(--radius-s); font-size: 14px;
background: #E6F0DC; color: #3D6B1F;
}
.flash-item.err { background: #F8DDDA; color: #A1341F; }
.empty {
background: var(--surface); border: 1px dashed var(--line);
border-radius: var(--radius); padding: 40px 20px; text-align: center;
color: var(--muted);
}
.empty b { display: block; color: var(--ink); font-weight: 500; margin-bottom: 4px; }
/* ---------------- 登录 / 安装 ---------------- */
.auth-wrap {
min-height: 100vh; display: flex; align-items: center; justify-content: center;
padding: 40px 20px;
}
.auth-card {
width: 100%; max-width: 400px; background: var(--surface);
border: 1px solid var(--line); border-radius: 20px; box-shadow: var(--shadow-l);
padding: 32px;
}
/* 图标 + 「用手机号登录你的家书」+ 家庭名,整块水平居中。
⚠️ 只有这一块居中:下面的表单仍然左对齐(label 左对齐才好读),
「忘记密码」那行本来就是居中,所以视觉上不会歪。 */
.auth-head { text-align: center; }
.auth-card .logo {
display: block; margin: 0 auto 14px;
width: 56px; height: 56px; border-radius: 14px;
}
.auth-card h1 { font-size: 20px; font-weight: 600; margin: 0 0 4px; }
.auth-card .lead { color: var(--muted); font-size: 14px; margin: 0 0 24px; }
.auth-card .btn { width: 100%; margin-top: 20px; }
.auth-foot { margin-top: 18px; font-size: 13px; color: var(--muted); text-align: center; }
/* ---------------- 其他 ---------------- */
.muted { color: var(--muted); }
.small { font-size: 13px; }
.nowrap { white-space: nowrap; }
.search { display: flex; gap: 8px; margin-bottom: 16px; }
.search .input { max-width: 260px; }
/* ---------------- 日期 + 时:分:秒 组合控件(templates/_datetime.php) ----------------
原生 datetime-local 的显示格式由浏览器 UI 语言/系统时间格式决定,
中文环境渲染成「下午 03:30」且没有秒,所以自己拼一个。
日期部分仍是原生 date 控件 → app.js 的 showPicker 让它整框可点。 */
.dt-input { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.dt-input .dt-date {
flex: 1 1 150px; min-width: 0;
padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); color: var(--ink); font: inherit;
accent-color: var(--brand);
}
.dt-input .dt-date { cursor: pointer; } /* 整框可点,光标也要说明这一点 */
.dt-input .dt-date::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .65; }
.dt-time { display: flex; align-items: center; gap: 3px; }
.dt-input .dt-hour,
.dt-input .dt-minute,
.dt-input .dt-second {
padding: 9px 6px; border: 1px solid var(--line); border-radius: var(--radius-s);
background: var(--surface); color: var(--ink); font: inherit;
font-variant-numeric: tabular-nums; cursor: pointer;
}
.dt-input .dt-colon { color: var(--muted); font-weight: 600; }
/* 「以哪个生日为准」的两块:被选中的那块描一圈,一眼看出填的是哪边 */
.bd-source { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.bd-pane { border: 1px solid transparent; border-radius: var(--radius); padding: 10px 12px 2px; }
.bd-pane.is-primary { border-color: var(--brand-2); background: #FDF6F1; }
.bd-pane .bd-pane-k { font-size: 13px; font-weight: 600; margin: 0 0 8px; }
.bd-readonly {
display: inline-block; padding: 6px 10px; border-radius: var(--radius-s);
background: var(--line-2); color: var(--muted); font-size: 14px;
font-variant-numeric: tabular-nums;
}
/* ---------------- 称谓标签 ---------------- */
.rel-label {
display: inline-block; padding: 1px 9px; border-radius: 999px;
background: #F6E3D7; color: var(--brand-ink);
font-size: 11px; font-weight: 500; line-height: 1.8;
margin-right: 2px;
}
/* ---------------- 家谱(以我为中心) ---------------- */
.tree { display: flex; flex-direction: column; align-items: center; gap: 0; }
.tree-gen { width: 100%; }
.tree-gen-label {
display: flex; align-items: center; gap: 8px;
margin: 0 0 8px; padding-left: 2px;
}
.tree-gen-label .gname { font-size: 13px; font-weight: 600; color: var(--ink); }
.tree-gen-label .gtag {
font-size: 11px; color: var(--muted); background: var(--line-2);
padding: 1px 8px; border-radius: 999px;
}
.tree-row {
display: flex; flex-wrap: wrap; gap: 10px;
justify-content: center; align-items: stretch;
}
.tnode {
display: flex; flex-direction: column; align-items: center; gap: 5px;
width: 104px; padding: 12px 8px 10px; text-align: center;
background: var(--surface); border: 1px solid var(--line);
border-radius: 14px; box-shadow: var(--shadow);
color: var(--ink); transition: transform .15s ease, box-shadow .15s ease;
}
.tnode:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.tnode .tph { width: 52px; height: 52px; border-radius: 50%; overflow: hidden; background: #F1E7D6; }
.tnode .tph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tnode .tname { font-size: 13px; font-weight: 600; line-height: 1.3; }
.tnode .tlabel { font-size: 11px; color: var(--brand); }
.tnode .tph-mark { font-size: 10px; color: var(--faint); }
.tnode.is-me {
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(168, 74, 40, .12), var(--shadow);
}
.tnode.is-me .tlabel { color: var(--brand-ink); font-weight: 600; }
.tnode.is-placeholder { border-style: dashed; }
/* 世代之间的连接线(纯装饰,说明"往下走一代") */
.tree-link {
width: 2px; height: 22px; background: linear-gradient(var(--line), var(--line-2));
margin: 6px auto;
}
/* 详情页里的迷你家谱 */
.mini-tree { display: flex; flex-direction: column; gap: 8px; }
.mini-gen { display: flex; flex-wrap: wrap; gap: 8px; }
.mini-node {
display: flex; align-items: center; gap: 7px;
padding: 5px 12px 5px 5px; border-radius: 999px;
background: #FAF7F1; border: 1px solid var(--line);
color: var(--ink); font-size: 13px;
}
.mini-node:hover { text-decoration: none; border-color: var(--brand); }
.mini-node img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }
.mini-node .mn-label { font-size: 11px; color: var(--brand); }
.mini-node.is-me { border-color: var(--brand); background: #F6E3D7; }
/* ---------------- 亲属关系编辑 ---------------- */
.rel-summary {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px; margin-bottom: 18px;
}
.rel-sum-item {
background: #FAF7F1; border: 1px solid var(--line-2);
border-radius: var(--radius-s); padding: 10px 14px;
}
.rel-sum-k { display: block; font-size: 12px; color: var(--muted); margin-bottom: 3px; }
.rel-sum-v { font-size: 14px; }
.rel-sum-v a { margin-right: 6px; }
.rel-add { border-top: 1px solid var(--line-2); padding-top: 16px; }
.rel-add-title { font-size: 14px; font-weight: 600; margin: 0 0 12px; }
.rel-form {
display: grid; grid-template-columns: 1fr 1fr 1.4fr auto;
gap: 12px; align-items: end;
}
.rel-list { margin-top: 16px; font-size: 13px; }
.rel-list summary { cursor: pointer; color: var(--muted); }
.rel-list ul { margin: 10px 0 0; padding-left: 18px; }
.rel-list li { margin-bottom: 6px; }
.rel-list .btn { margin-left: 6px; }
/* ---------------- 三角色 / 存储设置 ---------------- */
.role-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.role-card { background: #FAF7F1; border: 1px solid var(--line-2); border-radius: var(--radius-s); padding: 14px 16px; }
.rc-name { font-weight: 600; margin-bottom: 5px; }
.rc-desc { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.65; }
.radio-row { display: flex; flex-direction: column; gap: 10px; }
.radio-item {
display: flex; gap: 10px; align-items: flex-start;
background: #FAF7F1; border: 1px solid var(--line-2);
border-radius: var(--radius-s); padding: 12px 14px; cursor: pointer;
}
.radio-item input { margin-top: 3px; }
.check-item { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; cursor: pointer; }
.check-item input { margin-top: 3px; }
.oss-box {
border: 1px solid var(--line-2); border-radius: var(--radius-s);
padding: 12px 16px; background: #FCFAF6;
}
.oss-box summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--muted); }
.plain-list { margin: 0; padding-left: 18px; font-size: 14px; }
.plain-list li { margin-bottom: 7px; }
@media (max-width: 720px) {
.rel-form { grid-template-columns: 1fr; }
.tnode { width: 88px; padding: 9px 6px 8px; }
.tnode .tph { width: 44px; height: 44px; }
.tree-row { gap: 8px; }
}
@media (max-width: 720px) {
/* 390px 宽度放不下整套顶栏:只留 图标 + 导航 + 头像 + 退出 */
.topbar-inner { gap: 6px; padding: 0 10px; height: 54px; }
.brand img { width: 26px; height: 26px; }
.nav { gap: 0; }
.nav a { padding: 6px 8px; font-size: 13px; }
.topbar-right { gap: 6px; }
.who-name, .who-pwd { display: none; } /* 名字与「改密码」收起,设置页里都有 */
.who img { width: 26px; height: 26px; }
.container, .container-narrow { padding: 20px 16px 48px; }
.form-grid { grid-template-columns: 1fr; }
.detail { grid-template-columns: 1fr; }
.hero { padding: 20px; gap: 14px; }
.hero h1 { font-size: 20px; }
.hero-stats { margin-left: 0; gap: 20px; }
.page-head { flex-wrap: wrap; align-items: center; }
.page-head .actions { margin-left: 0; width: 100%; }
.kv-row { grid-template-columns: 84px 1fr; }
.search { flex-wrap: wrap; }
.search .input { max-width: none; flex: 1 1 auto; }
}
/* ---------------- 「我怎么称呼家人」(视角属性,见 app/Nickname.php) ---------------- */
.nick-list { display: flex; flex-direction: column; gap: 8px; }
.nick-row {
display: grid; grid-template-columns: minmax(120px, auto) 1fr auto;
gap: 10px; align-items: center;
padding: 8px 10px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.nick-who { display: flex; align-items: center; gap: 8px; font-size: 14px; min-width: 0; }
.nick-who img { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.nick-who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 详情页上的单个"我叫他什么"输入行 */
.nick-inline { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.nick-inline .input { flex: 1 1 220px; max-width: 340px; }
@media (max-width: 720px) {
.nick-row { grid-template-columns: 1fr; }
.nick-inline .input { max-width: none; }
}
/* ---------------- 连通性测试与诊断(设置页) ---------------- */
.test-actions { display: flex; flex-direction: column; gap: 10px; }
.test-actions form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.oss-diag {
margin-top: 16px; border-radius: var(--radius-s); overflow: hidden;
border: 1px solid var(--line);
}
.oss-diag-head {
display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
padding: 10px 14px; font-size: 13px;
background: var(--line-2);
}
.oss-diag.is-ok .oss-diag-head { background: #E6F0DC; color: #3F6021; }
.oss-diag.is-bad .oss-diag-head { background: #F6E3D7; color: var(--brand-ink); }
.oss-diag-table { width: 100%; border-collapse: collapse; font-size: 13px; background: var(--surface); }
.oss-diag-table th,
.oss-diag-table td { padding: 8px 12px; text-align: left; vertical-align: top; border-top: 1px solid var(--line-2); }
.oss-diag-table th { font-weight: 500; color: var(--muted); font-size: 12px; }
.oss-diag-table td:nth-child(1) { white-space: nowrap; }
.oss-diag-table td:nth-child(3) { white-space: nowrap; }
.oss-diag-table tr.row-ok td:nth-child(2) { color: #4A7226; }
.oss-diag-table tr.row-bad td:nth-child(2) { color: var(--red); }
@media (max-width: 720px) {
.oss-diag-table th:nth-child(3),
.oss-diag-table td:nth-child(3) { display: none; }
}
/* ============================================================
以下都是 2026-09-30 那一批反馈加的东西
============================================================ */
/* 只给读屏用的标签:视觉上不占地,但无障碍名还在(别用 display:none,那会一起藏掉) */
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* 区块之间的小间隔(比写 style="margin-top:20px" 好读) */
.stack-gap { height: 20px; }
/* ---------------- 名册:按关系分组分栏 ---------------- */
.group-block { margin-top: 24px; }
.group-head {
display: flex; align-items: baseline; gap: 8px;
margin: 0 0 10px; padding-bottom: 8px;
border-bottom: 1px solid var(--line);
font-size: 15px; font-weight: 600;
}
.group-head .group-title { color: var(--ink); }
/* 分组名前面那道砖红短线,让分栏在长页面上更容易一眼定位 */
.group-head .group-title::before {
content: ''; display: inline-block; vertical-align: middle;
width: 3px; height: 13px; margin-right: 8px; border-radius: 2px;
background: var(--brand);
}
.group-head .group-n { margin-left: auto; font-weight: 400; font-size: 12px; color: var(--faint); }
/* 卡片改成 div > a + details:表单不能套在链接里(非法 HTML,浏览器会把 DOM 拆开) */
.person-main { display: block; color: var(--ink); }
.person-main:hover { text-decoration: none; }
.person-more {
border-top: 1px solid var(--line-2); background: #FCFAF6;
}
.person-more > summary {
list-style: none; cursor: pointer; padding: 7px 14px;
font-size: 12px; color: var(--muted);
}
.person-more > summary::-webkit-details-marker { display: none; }
.person-more > summary::after { content: ' ▾'; }
.person-more[open] > summary::after { content: ' ▴'; }
.person-more > summary:hover { color: var(--brand); }
.person-move { display: flex; gap: 6px; padding: 0 14px 12px; }
.person-move .select { flex: 1 1 auto; min-width: 0; padding: 6px 8px; font-size: 13px; }
.person-move .btn { flex: 0 0 auto; }
/* ---------------- 个人设置:分组顺序 ---------------- */
.order-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.order-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 12px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.order-no {
flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
background: var(--brand); color: #fff;
font-size: 12px; line-height: 22px; text-align: center;
}
.order-name { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.order-count { flex: 0 0 auto; font-size: 12px; color: var(--faint); }
.order-move { flex: 0 0 auto; display: flex; gap: 4px; }
.order-move .btn { padding: 2px 8px; line-height: 1.4; }
.order-move .btn[disabled] { opacity: .35; cursor: default; }
/* ---------------- 个人设置:关系分类管理 ---------------- */
.inline-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.inline-form .input { flex: 1 1 200px; max-width: 320px; }
.group-manage { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.group-manage-row {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 8px 12px; border-radius: var(--radius-s);
background: #FAF7F1; border: 1px solid var(--line-2);
}
.group-rename { display: flex; gap: 8px; align-items: center; flex: 1 1 240px; min-width: 0; }
.group-rename .input { flex: 1 1 auto; min-width: 0; max-width: 220px; padding: 6px 10px; font-size: 13px; }
.group-manage-row .group-count { flex: 0 0 auto; }
.del-wrap > summary { list-style: none; cursor: pointer; }
.del-wrap[open] { flex: 1 1 100%; }
.del-confirm { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 8px 0 2px; }
.btn.danger { color: var(--red); border-color: rgba(192, 83, 63, .35); }
.btn.danger:hover { background: rgba(192, 83, 63, .08); }
/* ---------------- 图片裁剪浮层 ---------------- */
/* 覆盖层默认不占空间;打开时铺满整屏,把底下的页面锁住(裁剪时不该能点到别处) */
.crop-overlay { display: none; }
.crop-overlay.is-open {
display: flex; position: fixed; inset: 0; z-index: 100;
align-items: center; justify-content: center;
padding: 20px; background: rgba(44, 35, 29, .55);
}
.crop-panel {
background: var(--surface); border-radius: var(--radius);
box-shadow: var(--shadow-l); padding: 16px;
max-width: calc(100vw - 24px); max-height: calc(100vh - 24px); overflow: auto;
}
.crop-head {
display: flex; align-items: baseline; gap: 8px;
font-size: 15px; font-weight: 600; margin-bottom: 12px;
}
.crop-head .crop-shape { font-size: 12px; font-weight: 400; color: var(--muted); }
.crop-stage {
position: relative; margin: 0 auto; overflow: hidden;
background: #F1E7D6; border-radius: var(--radius-s);
touch-action: none; /* 手机上拖框时别把页面一起滚走 */
user-select: none;
}
.crop-img { display: block; width: 100%; height: 100%; }
/* 框外的部分压暗 —— 这是"你只留下框里这块"最直观的表达 */
.crop-box {
position: absolute; cursor: move;
border: 2px solid #fff;
box-shadow: 0 0 0 9999px rgba(44, 35, 29, .5);
}
.crop-box::before {
content: ''; position: absolute; inset: 0;
border: 1px dashed rgba(255, 255, 255, .75); pointer-events: none;
}
.crop-handle {
position: absolute; right: -8px; bottom: -8px;
width: 20px; height: 20px; border-radius: 50%;
background: #fff; border: 2px solid var(--brand);
cursor: nwse-resize;
}
.crop-tip { color: var(--muted); font-size: 12px; margin: 12px 0 0; }
.crop-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; }
@media (max-width: 720px) {
.order-move .btn { padding: 4px 10px; }
.group-manage-row { align-items: flex-start; }
.crop-panel { padding: 12px; }
}