Files
FamilyBook/assets/css/app.css
T
laoyang ff353b1eee v8:观影落地(MovieLog 整体迁入统一事件原语)+ 三处 UI 撤除
- Schema v8:events / movie_details / event_attendees / event_photos 四张表,
  uk_event_source(family_id, source_ref) 作迁移幂等锚
- Movie 模型:时间线/详情/增删改、同行人整组替换、照片逐张管理、感受英→中映射
- ImageSlot 新增 poster(2:3) / ticket(free) / photo(free) 三槽位;
  free 槽位不裁剪、整图等比缩进框(只缩不放大)
- OssDriver::copyObject:同桶 CopyObject(手写 V1 签名,x-oss-* 头按字典序进签名串)
- 路由:movies 时间线/详情/新增/编辑/删除/补照片/删照片;同行人按家庭过滤;
  访客只读(requireWrite)
- UI 撤除:首页「我的一句话」、家谱页「补充我的亲属关系 / 看整个名册」两个按钮;
  顶栏加「观影」入口
- 修潜伏 bug:ImageSlot::parseRatio 的正则用 / 作分隔符,字符类里的 / 把模式
  提前截断(Unknown modifier ']')—— ratio() 只在上传 POST 里被调到,一直没被观察到
- 线上数据迁移(临时 token 门禁页,用完即删):MovieLog 219 条观影 → events,
  14 位观影人新增进名册(带 MovieLog 头像),431 个 OSS 对象同桶拷贝
  (movielog/uploads/<dir>/x → family/<dir>/x),review/feelings/影院三段/照片全量搬
2026-09-30 20:22:46 +08:00

832 lines
37 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); 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; }
}
/* ===== 观影(v8,MovieLog 迁入) ===== */
.movie-list { display: flex; flex-direction: column; gap: 10px; }
.movie-card {
display: flex; gap: 14px; align-items: center;
padding: 10px; background: var(--card-bg, #fff);
border: 1px solid var(--line); border-radius: 12px;
text-decoration: none; color: var(--ink);
}
.movie-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-l); text-decoration: none; }
.m-poster {
flex: 0 0 64px; width: 64px; height: 96px; object-fit: cover;
border-radius: 8px; background: var(--line-2); display: block;
}
.m-poster-empty {
display: flex; align-items: center; justify-content: center;
color: var(--faint, #B9AE9F); font-size: 20px; font-weight: 600;
}
.m-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.m-title { font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rating {
background: var(--brand); color: #fff; border-radius: 999px;
padding: 1px 8px; font-size: 12px; font-weight: 600;
font-variant-numeric: tabular-nums;
}
.rating-lg { font-size: 14px; padding: 2px 10px; }
.m-meta { color: var(--muted); font-size: 13px; }
.m-who { color: var(--muted); font-size: 12px; }
.movie-detail { display: grid; grid-template-columns: 220px 1fr; gap: 24px; align-items: start; }
.m-poster-col { display: flex; flex-direction: column; gap: 12px; }
.m-poster-lg {
width: 100%; aspect-ratio: 2 / 3; object-fit: cover;
border-radius: 12px; background: var(--line-2); display: flex;
align-items: center; justify-content: center;
color: var(--faint, #B9AE9F); font-size: 40px; font-weight: 600;
}
a .m-poster-lg { display: block; }
.m-ticket { width: 100%; border-radius: 10px; border: 1px solid var(--line); display: block; }
.m-info .m-score { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.badge-feel { background: #F6E3D7; color: var(--brand-ink); }
.m-block { margin-top: 16px; }
.m-block h3 { font-size: 14px; margin: 0 0 6px; color: var(--muted); font-weight: 500; }
.m-block p { margin: 0; font-size: 14px; line-height: 1.7; white-space: normal; }
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }
.photo-cell { position: relative; border-radius: 10px; overflow: hidden; border: 1px solid var(--line); }
.photo-cell img { width: 100%; height: 160px; object-fit: cover; display: block; }
.photo-del {
position: absolute; top: 6px; right: 6px;
width: 24px; height: 24px; border-radius: 50%; border: 0;
background: rgba(0,0,0,.55); color: #fff; font-size: 16px; line-height: 1;
cursor: pointer; padding: 0;
}
.photo-del:hover { background: var(--red, #C0533F); }
.photo-add label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 8px; }
.photo-add-row { display: flex; gap: 10px; align-items: center; }
.photo-add-row .input { flex: 1; }
.feel-grid { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.feel-item { display: flex; align-items: center; gap: 5px; font-size: 14px; cursor: pointer; }
.feel-item input { accent-color: var(--brand); }
@media (max-width: 720px) {
.movie-detail { grid-template-columns: 1fr; }
.m-poster-col { flex-direction: row; }
.m-poster-col .m-poster-lg, .m-poster-col a:first-child { flex: 0 0 140px; width: 140px; }
.m-ticket { width: auto; max-width: 55%; }
.photo-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
.photo-cell img { height: 120px; }
}