- ① 头像上传失败(超限/非图片)不再挡保存,改为 flash 提示「已加进名册。头像没有保存:…」 ——这是「添加不了」的真根因(错误被塞进 errors.avatar 把整条新增打回) - ② 表单顺序:生日整块下移(身份证能读出生日时无需常改) - ③ 裁剪缩放语义反转:框在屏幕上的位置与大小不动、动它底下的图; 落库归一化矩形随倍率缩小(放大=同一块区域看得更清楚) - ④ 取消应用层 5MB 兆数上限;按像素动态上调 memory_limit(px*6+64M,封顶 768M); 头像输出恒为 512x512 - ⑤ 成员信息对齐 vCard:person_contacts 表(phone/email/address/url 可多行, value 加密落库)+ persons.org(工作单位)/title(职务职称);均可留空 Schema v5→v6:person_contacts 建表 + persons.org/title 加列
761 lines
33 KiB
CSS
761 lines
33 KiB
CSS
/* ============================================================
|
||
家书 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); position: relative;
|
||
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; }
|
||
|
||
/* ---------------- 名册:拖动排序 / 拖到别栏改分组 ----------------
|
||
拖动是"锦上添花":没有它,卡片上的「⋯ → 调整分组」表单照样能改分组,
|
||
手机上 HTML5 拖放本来也不生效,那套表单就是窄屏的正路。 */
|
||
.roster.is-droppable .person { cursor: grab; }
|
||
.roster.is-droppable .person.is-dragged { opacity: .45; cursor: grabbing; }
|
||
.drag-handle {
|
||
position: absolute; left: 8px; top: 8px; z-index: 2;
|
||
width: 22px; height: 22px; line-height: 22px; text-align: center;
|
||
border-radius: 6px; font-size: 14px;
|
||
color: var(--muted); background: rgba(255, 255, 255, .82);
|
||
border: 1px solid var(--line-2);
|
||
pointer-events: none; /* 手柄只是个记号,抓取要落在整张卡上 */
|
||
}
|
||
.drag-handle-inline { color: var(--muted); }
|
||
|
||
/* 空栏平时收起来(七个预置分组可能只用到三个,摊开会显得名册破破烂烂)——
|
||
但**拖动时必须露出来**,否则"想拖到一个现在没人的分组里"就没地方放。 */
|
||
.group-block.is-empty { display: none; }
|
||
.roster.is-dragging .group-block.is-empty {
|
||
display: block; opacity: .9;
|
||
border: 1px dashed var(--line);
|
||
border-radius: var(--radius); padding: 10px; margin-top: 18px;
|
||
background: rgba(250, 247, 241, .6);
|
||
}
|
||
.group-drop-hint { display: none; margin-left: auto; font-weight: 400; font-size: 12px; color: var(--brand); }
|
||
.roster.is-dragging .group-block .group-drop-hint { display: inline; }
|
||
/* 拖动时把"有多少人"让出位置给落点提示,两者都挤在右上角会很乱 */
|
||
.roster.is-dragging .group-block .group-n { display: none; }
|
||
.roster.is-dragging .people-grid { min-height: 24px; }
|
||
|
||
/* 拖完/没拖成的轻提示(2 秒自己消失,不打断操作) */
|
||
.roster-note {
|
||
position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 12px);
|
||
z-index: 200; padding: 8px 16px; border-radius: 999px;
|
||
background: rgba(44, 35, 29, .88); color: #fff; font-size: 13px;
|
||
opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
|
||
}
|
||
.roster-note.is-show { opacity: 1; transform: translate(-50%, 0); }
|
||
.roster-note.is-err { background: rgba(178, 45, 45, .92); }
|
||
|
||
/* ---------------- 分享名片(成员详情页) ---------------- */
|
||
.share-wrap { display: inline-block; }
|
||
.share-wrap > summary { list-style: none; cursor: pointer; display: inline-block; }
|
||
.share-wrap > summary::-webkit-details-marker { display: none; }
|
||
.share-wrap .inline-form { margin-top: 10px; }
|
||
|
||
/* ---------------- 「从家里已有的人里挑」 ---------------- */
|
||
.pick-list { list-style: none; margin: 0 0 4px; padding: 0; display: grid; gap: 8px; }
|
||
.pick-row { margin: 0; }
|
||
.pick-item {
|
||
display: flex; align-items: center; gap: 10px; cursor: pointer;
|
||
padding: 8px 12px; border-radius: var(--radius-s);
|
||
background: #FAF7F1; border: 1px solid var(--line-2);
|
||
}
|
||
.pick-item:hover { border-color: var(--brand); }
|
||
.pick-item input[type="checkbox"] { width: 16px; height: 16px; flex: 0 0 auto; accent-color: var(--brand); }
|
||
.pick-ph { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; background: #F1E7D6; }
|
||
.pick-name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||
|
||
/* ---------------- 个人设置:分组顺序 ---------------- */
|
||
.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: auto;
|
||
background: #F1E7D6; border-radius: var(--radius-s);
|
||
touch-action: none; /* 手机上拖框时别把页面一起滚走 */
|
||
user-select: none;
|
||
}
|
||
/* 图片的宽高由 JS 按倍率写死(放大后比舞台大,靠舞台自身滚动看别处)——
|
||
所以这里**不能**写 width/height:100%,否则缩放就永远没效果。 */
|
||
.crop-img { display: block; max-width: none; }
|
||
/* 缩放条 —— 用户明确要求"裁剪的时候要能缩放" */
|
||
.crop-zoom-row {
|
||
display: flex; align-items: center; gap: 10px;
|
||
margin-top: 12px; font-size: 13px; color: var(--muted);
|
||
}
|
||
.crop-zoom { flex: 1; min-width: 120px; accent-color: var(--brand); }
|
||
.crop-zoom-val { font-variant-numeric: tabular-nums; min-width: 42px; text-align: right; }
|
||
/* 框外的部分压暗 —— 这是"你只留下框里这块"最直观的表达 */
|
||
.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; }
|
||
}
|
||
|
||
/* ===== 补充联系方式(可多条):标注 + 内容 一行两组,窄屏摞起来 ===== */
|
||
.ct-row { display: flex; gap: 8px; margin-bottom: 8px; }
|
||
.ct-row .ct-label { flex: 0 0 140px; }
|
||
.ct-row .input { flex: 1; }
|
||
@media (max-width: 720px) {
|
||
.ct-row { flex-direction: column; gap: 6px; }
|
||
.ct-row .ct-label { flex: none; }
|
||
}
|