v24 第 20 批:拖动即时上传 / 观影图片带出 / 行程保存修复 / 日程红线与删除范围与多提醒 / 悬停统一(用户 9 条)

用户 9 条原话要点:
  ①「观影模块,编辑观影,已经保存的海报和票根、合影,都没带进来。」
  ②「个人设置页面,"只影响你自己看到的样子"这个提示删除,不要了。我怎么称呼家人,
     这个卡片,不要了。我的资料这个卡片,不要了。」
  ③「旅程模块,编辑行程,点击保存没有反应。」
  ④「所有上传图片视频和附件的模块,都允许多次拖动添加文件上传,拖动一个进去,
     就开始上传,还可以继续拖动,继续上传,最后统一保存即可。」
  ⑤「添加行程,线路名、承运方,都不要了。车辆,只在出行方式选择自驾时才出现,
     其他情况下默认不出现。出发地和到达地,应该在同一行。」
  ⑥「日程模块,周视图和日视图,应该在视图中间显示一条红线,用来表示当前进行的时间…
     用一个不断从上到下的红线,来表示当前时间在视图上的刻度和位置。」
  ⑦「对于重复的日程,当点击删除的时候,应该提示是删除这一次,还是全部都删除,
     还是删除这一次及以后的。」
  ⑧「日程的提醒,是可以多选的,就是说,一个日程,我可以选择让它 5 分钟提醒,
     10 分钟提醒等等。」
  ⑨「很多按钮的悬停效果非常奇怪,感觉纯粹是换了一个背景色…应该调整所有按钮的悬停效果,
     保持统一,但是不要单纯的换一个不协调的背景色。」

🔴 三个真根因(都是"看着不像 bug"的地方):
  1. **③ 点保存没反应 = `<form>` 嵌套**(HTML 非法):`trips/node_form.php` 的照片缩略图上
     套了 `<form action=photo_del>`,而整块在**外层行程表单内部**。浏览器解析到内层
     `</form>` 时**把外层表单提前关掉** → 「保存」按钮**掉出表单**(真机实测
     `button.form === null`)。源码里完全看不出,**只有真浏览器量 `btn.form` 才现形**;
     且**只有这段行程已经有照片时才触发** —— 所以表现为"有时候好、有时候坏"。
     → 按钮留在原位(布局不动),用 HTML5 `form="id"` 关联到**表单外**的独立表单。
  2. **⑤ 车辆藏不住(第 17 批就写过、一直没生效)**:`.field` 自己声明 `display:flex`,
     作者样式压过浏览器默认的 `[hidden]{display:none}` → `hidden` 形同虚设。
     **本项目第 4 次栽同一个坑**(.att-item / .pv / .pp-item 各一次)→ 补 `.field[hidden]`。
  3. **④ 拖动即时上传的卡片"看不见"**:模板给 `.stage-list` 加了 `hidden`(没人上传时
     不留空边框),JS 建卡片时忘了 `list.hidden = false` → 卡片在 DOM 里但整条清单
     `display:none`、尺寸 0×0。**HTTP 层断言全绿**(HTML 里确实有卡片),
     **只有真机量尺寸才发现** —— 由新写的真机探针抓到。

本批新增能力:
- **拖动即时上传**(第 4 条):新增 `upload_stage.php` + `app/StagedUpload.php` +
  `templates/_stage_drop.php`。拖进来 → **立刻** POST 到暂存目录 → 回 `stoken` →
  放进隐藏字段 `stage[]` → 保存时在 `attach_save_files()` 里**认领**(移文件 + 建附件行)。
  `media.php` 加 `?stage=` 预览(拖完立刻看得见缩略图)。
  ⚠️ 三个必须踩准:**串行队列**(并发会让缩略图顺序乱跳)、**提交守卫**
  (还有没传完就 preventDefault,否则"拖了但没保存上"必然发生)、**卡片清单要 un-hide**。
  ⚠️ 海报/票根走**独立字段名** `stage_poster`/`stage_ticket`(各自单槽位、要按比例重裁)。
  ⚠️ 落点**就近取**(`input.closest('.field')`):一个表单里多个上传区不能串到第一个去。
- **观影编辑带出已存图片**(第 1 条):编辑页显示海报 / 票根 / 合影 + 各自的删除入口;
  新增 `movies/media_del`(只删不换也能删,槽位走白名单);`movies/update` 收编辑页
  新选的照片时**合并**而不是替换(否则一编辑就把已有合影冲掉)。
- **行程表单**(第 5 条):去掉线路名 / 承运方(**库里的值保留**:UPDATE 时不参与,
  与 seat_info/gate 同一套);出发地/到达地包进 `.loc-row` 真同一行;车辆按自驾显隐。
- **当前时间红线**(第 6 条):位置在服务端算(与 `.tg-block` 同一套"24h = 100%"坐标),
  前端每分钟自己挪(对齐到下一分钟整再起定时器),跨午夜 reload;只画在**今天那一列**。
- **重复日程删除三档**(第 7 条):与「编辑」那三档同名同序(全部 / 这一次及以后 /
  只这一次)。「只删这一次」→ `skipOnce`(**绝不删整条系列**);「这一次及以后」→
  老系列截 UNTIL,**必须清掉 COUNT**(两者并存时 COUNT 优先,不清就截不断)。
- **提醒多选**(第 8 条):同列存逗号串(`5,15,60`);Schema 把它从 **INT 改 VARCHAR**
  (INT 存逗号串会被隐式转换,多档会静默只剩第一档);ICS 导出吐**多个 VALARM**。
- **悬停统一**(第 9 条):原来 14 种互不相干的 hover 底色 → 统一成**两套手法**:
  有底色 `filter: brightness(.92)` 同色系加深 + 轻抬升;描边白底 `--hov-veil` 中性薄雾
  + 边框微显。放在 CSS **最末**用一条兜底收口(新按钮自动继承),并把十几条旧规则
  清成"交给统一段"。`person_picker.js` 同时改为按需加载。

验证(本地全套):
- e2e **1004/0**(+21 条本批断言)· 升级 149/0 · 桌面几何 428/0 ·
  **真机探针 25/0**(连续拖两次 / 红线位置与颜色与几何 / 车辆显隐 / 出发到达同行)。
- **反向验证 5/5**:①删除按钮改回嵌套 form ②卡片清单不 un-hide ③观影编辑不渲染海报
  ④车辆隐藏失效 ⑤「只删这一次」做成删全部 —— 五种坏法全部被抓住,还原后复绿 1004/0。
  过程中还**抓到两条弱断言**(只查 DOM 有无、不查可见性/是否真渲染),已一并加强。
- 部署脚本静态断言段本地空跑 **305 条全过**。
This commit is contained in:
2026-10-06 03:14:18 +08:00
parent 1756c6265d
commit c3d00ad66a
20 changed files with 1656 additions and 149 deletions
+245 -18
View File
@@ -81,7 +81,7 @@ a:hover { text-decoration: underline; }
.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-menu > summary:hover .who-name { color: var(--brand); }
.who-menu[open] > summary { background: var(--line-2); }
.who-menu[open] > summary .caret { transform: rotate(180deg); }
@@ -139,7 +139,7 @@ a:hover { text-decoration: underline; }
display: inline-flex; align-items: center; justify-content: center;
line-height: 1;
}
.btn-icon:hover { background: #FAF7F1; color: var(--brand); border-color: var(--brand); }
.btn-icon:hover { color: var(--brand); border-color: var(--brand); } /* 底色交给统一段 */
.btn-icon svg { display: block; }
/* 观影设置菜单:从右上角改成**按钮下方右对齐**(按钮贴右,菜单也贴右才不会跑出屏外) */
@@ -269,7 +269,9 @@ a:hover { text-decoration: underline; }
transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.icon-btn:hover {
color: var(--brand); border-color: var(--brand-2); background: #FBF7F1;
color: var(--brand); border-color: var(--brand-2);
/* 底色交给文末「全站悬停统一」段(中性薄雾) */
background: var(--hov-veil, rgba(44,35,29,.05));
}
.icon-btn svg { display: block; }
@@ -438,22 +440,21 @@ textarea.input { min-height: 76px; resize: vertical; }
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:hover { text-decoration: none; } /* 底色交给文末「全站悬停统一」段(同色系加深) */
.btn-ghost { background: #fff; color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { background: #FAF7F1; }
.btn-ghost:hover { } /* 同上:中性薄雾 + 边框微显,见文末统一段 */
.btn-danger { background: #fff; color: var(--red); border-color: rgba(192,83,63,.35); }
.btn-danger:hover { background: #FDF1EE; }
.btn-danger:hover { } /* 同上(危险类走薄雾带一点红) */
/* v24 第 19 批 用户第 7 条:行程行的动作按钮要**看得出颜色**——
「修改是黄色背景,完成绿色,未成行红色」。
⚠️ 这三个是**实心**色块(不是 .btn-danger 那种淡红描边):用户说的是背景色,
要的是"一眼分得清哪个是哪个"。所以单独定义,不去改 .btn-danger 的既有语义
(全站十来处「删除」都在用它,改成实心会到处变样)。 */
.btn-warn { background: #E8A33D; color: #fff; border-color: transparent; }
.btn-warn:hover { background: #D79230; }
.btn-ok { background: #5E9E4E; color: #fff; border-color: transparent; }
.btn-ok:hover { background: #518B43; }
.btn-del { background: #C0533F; color: #fff; border-color: transparent; }
.btn-del:hover { background: #AC4835; }
/* ⚠️ 这三个的 hover 统一交给文件靠后的「第 20 批 全站悬停统一」那一段
(有底色的一律 `filter: brightness(.92)` 同色系加深,不再各写一个色值)。 */
.btn-sm { padding: 5px 12px; font-size: 13px; }
.btn-row { display: flex; gap: 10px; margin-top: 20px; }
@@ -1360,7 +1361,8 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
background: var(--brand); color: #fff; border-color: transparent;
}
.page-head .actions > .btn:not(.btn-icon):hover,
.page-head .actions > form > button.btn:not(.btn-icon):hover { background: #944022; }
/* 底色交给文末统一段(同色系加深) */
.page-head .actions > form > button.btn:not(.btn-icon):hover { }
/* 层 2:次要按钮(.btn-ghost)—— 品牌描边 + 极浅品牌底,比主按钮轻一档。
a / button 两种形态各写一条、特异性对齐(见层 1 的警告)。 */
@@ -1374,7 +1376,7 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
.page-head .actions > a.btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > button.btn-ghost:not(.btn-icon):hover {
background: #F7EDE4; border-color: var(--brand);
background: var(--hov-veil, rgba(44,35,29,.05)); border-color: var(--brand);
}
/* 层 3:危险按钮(.btn-danger)—— 淡红底 + 红字,同样要有颜色、但不能看着像主按钮。
@@ -1391,7 +1393,7 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
.page-head .actions > .btn-danger:not(.btn-icon):hover,
.page-head .actions > a.btn-danger:not(.btn-icon):hover,
.page-head .actions > form > .btn-danger:not(.btn-icon):hover,
.page-head .actions > form > button.btn-danger:not(.btn-icon):hover { background: #F7E4DF; }
.page-head .actions > form > button.btn-danger:not(.btn-icon):hover { background: rgba(192, 83, 63, .09); }
/* 「设置」图标按钮:**唯一无色的**(白底描边、方形),与上面所有彩色按钮区分。
padding 归零、做成正方形,但高度必须仍是 --act-h(= 文字按钮的 43.8px)——
写死过 40px,比旁边按钮矮 3.8px,是用户反复反馈的那个问题。 */
@@ -1403,12 +1405,13 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
}
.page-head .actions > .movie-set > summary.btn-icon:hover,
.page-head .actions > .movie-set > summary.btn:hover {
background: #FAF7F1; border-color: var(--brand); color: var(--brand);
border-color: var(--brand); color: var(--brand);
/* 底色交给文末统一段(中性薄雾),别在这里另糊一个 #FAF7F1 */
}
.movie-set[open] > summary.btn-icon,
.movie-set[open] > summary.btn { border-color: var(--brand); color: var(--brand); }
.btn.danger { color: var(--red); border-color: rgba(192, 83, 63, .35); }
.btn.danger:hover { background: rgba(192, 83, 63, .08); }
.btn.danger:hover { } /* 统一段处理 */
/* ---------------- 图片裁剪浮层 ---------------- */
/* 覆盖层默认不占空间;打开时铺满整屏,把底下的页面锁住(裁剪时不该能点到别处) */
@@ -1506,7 +1509,7 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
padding: 8px 20px; border-radius: 8px; font-weight: 600;
cursor: pointer; white-space: nowrap;
}
.search-bar button:hover { background: var(--brand-2); }
.search-bar button:hover { } /* 统一段处理(同色系加深,不换成 brand-2) */
.search-clear { color: var(--muted); font-size: 14px; padding: 8px 4px; white-space: nowrap; }
/* ---- 瀑布流海报墙 ---- */
@@ -1656,7 +1659,7 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
align-items: center; justify-content: center;
transition: background-color .2s ease; font-family: inherit;
}
.lb-btn:hover { background: rgba(255, 255, 255, .26); }
.lb-btn:hover { } /* 深底上不能用薄雾:走文末统一段的 brightness 提亮 */
.lb-btn[hidden] { display: none; }
.lb-close { top: 14px; right: 16px; width: 40px; height: 40px; border-radius: 50%; font-size: 18px; }
/* 下载按钮在关闭按钮左边,同高、同一套视觉(v24 第 3 条:预览层右上角要有下载) */
@@ -2199,7 +2202,7 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
background: none; border: none; cursor: pointer; color: var(--muted);
font-size: 14px; line-height: 1; padding: 4px 6px; border-radius: 6px;
}
.rv-del-btn:hover { background: rgba(192, 83, 63, .12); color: var(--red); }
.rv-del-btn:hover { color: var(--red); } /* 底色交给统一段 */
.rv-content {
margin: 8px 0 2px 44px; font-size: 15px; line-height: 1.75;
color: var(--ink); word-break: break-word;
@@ -2886,7 +2889,7 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
.seg-btn + .seg-btn { border-left: 0; }
.seg-btn:first-child { border-radius: var(--radius-s) 0 0 var(--radius-s); }
.seg-btn:last-child { border-radius: 0 var(--radius-s) var(--radius-s) 0; }
.seg-btn:hover { background: var(--surface-2, #FDFBF7); text-decoration: none; }
.seg-btn:hover { text-decoration: none; } /* 原来那层 #FDFBF7 在米色底上像脏了一块 */
.seg-btn.is-on {
background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}
@@ -3514,3 +3517,227 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
.pp-modal-box { max-height: 88vh; }
.pp-stage { min-height: 108px; max-height: 38vh; }
}
/* ============================================================
v24 第 20 批 用户第 9 条:**全站按钮悬停统一**
------------------------------------------------------------
用户原话:「很多按钮的悬停效果,非常奇怪,感觉纯粹是换了一个背景色,
这个背景色,跟这个模块完全不协调,应该调整所有按钮的悬停效果,
保持统一,但是不要单纯的换一个不协调的背景色。」
------------------------------------------------------------
⚠️ 改之前的问题(复现探针量出来 **15 种**互不相干的 hover 背景色):
#944022 / #FAF7F1 / #FBF7F1 / #F7EDE4 / #FDF1EE / #F7E4DF / #D79230 /
#518B43 / #AC4835 / var(--line-2) / var(--surface-2,#FDFBF7) / rgba(192,83,63,.08)
—— 同一行里「取消」和「保存」悬停变的是**两套不相干的色**;
`.seg-btn` 那种更是直接糊一层 `#FDFBF7`,在米色底上看着像"脏了一块"。
而且全是"只换背景"这一种表达 —— 这正是用户说的"纯粹是换了一个背景色"。
统一后的**一套手法**(所有按钮共用同一套手感,底色只跟着自己的色系走):
① **同色系加深**:有底色的一律 `filter: brightness(.92)` ——
深色按钮(品牌红 / 黄 / 绿 / 红)自然变深一点,**不再跳到另一个色**;
② **描边留白按钮**(.btn-ghost / .btn-danger / .btn-icon 及各处白底按钮):
统一用**中性薄雾** `rgba(44,35,29,.05)` —— 落在宣纸米底上只是"轻轻按了一下",
不会再出现 #F7EDE4 那种偏色的脏块;
③ **边框微显 + 极轻抬升**:`border-color: rgba(44,35,29,.16)` + `translateY(-1px)`
+ 一层很浅的投影 —— 给的是"这个能按"的触感,而不是换色;
④ 统一 `transition`(背景 / 边框 / 颜色 / 投影 / 位移一起过渡),避免"啪一下变色"。
⚠️ 实现上放在**文件最靠后**并带 `!important` 收口:
历史上有十几处散落的 hover(.btn:hover、.page-head 里那几层、.seg-btn:hover…)
优先级各不相同,逐个改既容易漏、又会被后加的规则盖掉。
收口之后**新加的按钮自动继承同一套手感**,作者不必再记一次。
⚠️ 深色浮层上的按钮(.lb-btn 在黑色遮罩上)不适用中性薄雾(深底上看不见),
由浮层自己的规则覆盖;.pv-nav / .pv-dl 同理,见各自定义处。
============================================================ */
:root {
/* 悬停手感三件套(改这里 = 全站按钮悬停一起变) */
--hov-lift: -1px;
--hov-ring: rgba(44, 35, 29, .16);
--hov-veil: rgba(44, 35, 29, .05);
--hov-dim: brightness(.92);
--hov-t: background-color .15s ease, border-color .15s ease,
color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.btn, .btn-ghost, .btn-danger, .btn-warn, .btn-ok, .btn-del, .btn-sm, .btn-icon,
button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-btn {
transition: var(--hov-t);
}
/* ① 有底色的按钮:同色系加深(不换色)+ 轻抬升
⚠️ 这里**只加 filter,绝不能写 `background-color: inherit`** ——
`inherit` 取的是**父元素**的背景(多半是透明),会把实心按钮变透明
(实测:黄/绿按钮悬停后变成 alpha 0.008 的鬼影)。
不改底色就"不写底色",让按钮自己的 background 留着。 */
.btn:not(.btn-ghost):not(.btn-danger):not(.btn-icon):not(.btn-warn):not(.btn-ok):not(.btn-del):hover,
.btn-warn:hover, .btn-ok:hover, .btn-del:hover,
.search-bar button:hover {
filter: var(--hov-dim);
transform: translateY(var(--hov-lift));
box-shadow: var(--shadow-l);
}
/* ② 描边留白按钮:中性薄雾 + 边框微显 + 轻抬升(不再各自糊一个偏色底) */
.btn-ghost:hover, .btn-icon:hover, .seg-btn:hover, .icon-btn:hover,
.who-menu > summary:hover,
.page-head .actions > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > a.btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > .btn-ghost:not(.btn-icon):hover,
.page-head .actions > form > button.btn-ghost:not(.btn-icon):hover {
background: var(--hov-veil) !important;
border-color: var(--hov-ring) !important;
transform: translateY(var(--hov-lift));
box-shadow: var(--shadow);
}
.page-head .actions > .movie-set > summary.btn-icon:hover,
.page-head .actions > .movie-set > summary.btn:hover {
background: var(--hov-veil) !important;
border-color: var(--hov-ring) !important;
}
/* ③ 危险 / 删除类:同一套手法,薄雾里带一点红(语义不丢,但不刺眼) */
.btn-danger:hover, .rv-del-btn:hover, .btn.danger:hover,
.page-head .actions > .btn-danger:not(.btn-icon):hover,
.page-head .actions > a.btn-danger:not(.btn-icon):hover,
.page-head .actions > form > .btn-danger:not(.btn-icon):hover,
.page-head .actions > form > button.btn-danger:not(.btn-icon):hover {
background: rgba(192, 83, 63, .09) !important;
border-color: rgba(192, 83, 63, .30) !important;
color: var(--red) !important;
transform: translateY(var(--hov-lift));
box-shadow: var(--shadow);
}
/* ⚠️ .btn-del 是**实心红**(第 19 批 行程行那种)—— 上面那条 ③ 会把它拉成淡红,
所以实心那一类要单独拉回来:不改底色(保住 #C0533F),只走"同色系加深"。 */
.btn-del:hover {
background: #C0533F !important;
color: #fff !important;
border-color: transparent !important;
}
/* ④ 纯图标 / 文字小按钮:不占背景,只做"轻抬升 + 轻微加深" */
.person-more-btn:hover, .rv-act-btn:hover, .bf-btn:hover, .cs-btn:hover,
.more-set > summary:hover, .rv-open-btn:hover {
filter: var(--hov-dim);
transform: translateY(var(--hov-lift));
text-decoration: none;
}
/* ⑤ 浮在深色遮罩上的按钮:深底上中性薄雾看不见 → 用"提亮"代替,
但**同一套手法**(变亮 + 轻抬升),保持全局手感一致。 */
.lb-btn:hover, .pv-close:hover, .crop-x:hover {
filter: brightness(1.18);
transform: translateY(var(--hov-lift));
}
/* v24 第 20 批 第 3 条:`form="id"` 关联用的**表单外壳**。
它们只承载字段、不出现在视觉里(按钮在原来的位置上,用 form= 指过来)。
⚠️ 必须 display:none —— 不藏的话页面底部会多出几块空的表单块。 */
.form-outside { display: none; }
/* ============================================================
v24 第 20 批 第 5 条:添加行程的三处版面调整
============================================================ */
/* ---- ① 出发地 / 到达地**同一行**(用户原话:"应该在同一行")----
⚠️ 必须显式开一个"一行两列"的容器,不能靠两个 .field 相邻 ——
.form-grid 是两列网格,每个 .field 各占一格;窄屏折行后顺序还会乱。 */
.loc-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 560px) {
/* 窄屏放不下并排两个地址框(中文地名 + 高德联想候选),改成上下 */
.loc-row { grid-template-columns: 1fr; }
}
/* ---- ② 「车辆」只在出行方式 = 自驾 时出现(第 17 批的**同一句原话**)----
🔴 v24 第 20 批 第 5 条修的是这里:用户又报了一次「车辆,只在出行方式选择自驾时
才出现,其他情况下,默认不出现」—— 说明**上一批的隐藏根本没生效**。
根因:模板里写的是 `<div class="field" data-mode-vehicle hidden>`,
而 `.field` 自己声明了 `display:flex` —— **作者样式优先于浏览器默认的
`[hidden]{display:none}`**,所以 `hidden` 形同虚设、车辆永远显示着。
(与 .pp-item / .pv 那几处是**同一个坑**,项目里已经栽过三次。)
修法:给自己声明过 display 的元素补一条 `[hidden]` 规则。 */
.field[hidden],
.field.full[hidden],
[data-mode-vehicle][hidden],
[data-mode-ticket][hidden] { display: none !important; }
/* ============================================================
v24 第 20 批 第 4 条:拖动即时上传的**暂存区** UI
------------------------------------------------------------
`.stage-drop` 是那个"可以往里拖"的落点;`[data-stage-list]` 装卡片。
卡片三态:.is-uploading(转圈感)/ 默认(缩略图)/ .is-failed(标红)。
============================================================ */
.stage-drop {
border: 1px dashed var(--line); border-radius: var(--radius-s);
background: #FDFBF7; padding: 10px 12px; margin-top: 6px;
}
.stage-drop.is-over { outline: 2px dashed var(--brand); outline-offset: 3px; background: #FDF3EC; }
.stage-drop-tip { display: block; font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.stage-list { display: flex; flex-wrap: wrap; gap: 8px; }
/* ⚠️ .stage-list 自己声明了 display:flex → 要藏就得补 [hidden](本项目老坑) */
.stage-list[hidden] { display: none; }
.stage-cell {
position: relative; width: 84px; padding: 6px 4px; border-radius: 8px;
border: 1px solid var(--line); background: var(--surface);
display: flex; flex-direction: column; align-items: center; gap: 3px;
font-size: 11px; text-align: center; box-sizing: border-box;
}
.stage-cell img { width: 72px; height: 54px; object-fit: cover; border-radius: 5px; display: block; }
.stage-cell .stage-ico {
width: 72px; height: 54px; border-radius: 5px; background: #5F6672; color: #fff;
display: flex; align-items: center; justify-content: center; font-weight: 700;
}
.stage-cell .stage-name {
max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink);
}
.stage-cell .stage-meta { color: var(--muted); }
.stage-cell.is-uploading { opacity: .7; }
.stage-cell.is-uploading .stage-meta { color: var(--brand); }
.stage-cell.is-failed { border-color: var(--red); background: #FDF1EE; }
.stage-cell.is-failed .stage-meta { color: var(--red); }
.stage-cell .stage-x {
position: absolute; top: -6px; right: -6px; width: 20px; height: 20px;
border-radius: 50%; border: 1px solid var(--line); background: #fff; color: var(--muted);
font-size: 13px; line-height: 1; cursor: pointer; padding: 0;
}
.stage-cell .stage-x:hover { color: #fff; background: var(--red); border-color: var(--red); }
/* ============================================================
v24 第 20 批 第 6 条:周 / 日视图的「当前时间红线」
------------------------------------------------------------
用户原话:「应该在视图中间显示一条红线,用来表示当前进行的时间,
用一个不断从上到下的红线,来表示当前时间在视图上的刻度和位置。」
⚠️ 用**绝对定位**(top 由服务端/JS 给百分比)—— 与 .tg-block 同一套坐标,
所以线正好落在时间刻度的位置上,不会与日程块错开。
⚠️ 只在**今天那一列**出现(`.is-today`),由模板决定渲染不渲染。
⚠️ `pointer-events: none`:这条线是"刻度提示",不该挡住它下面的日程点击
(用户点线附近想点的是日程,不是这条线)。
============================================================ */
.tg-now {
position: absolute; left: 0; right: 0; height: 0;
border-top: 2px solid #E0453B; /* 红线(用户点名"红线") */
z-index: 4; /* 在日程块(z-index 2)之上,要看得见 */
pointer-events: none;
}
/* 左端那个小圆点:让"这条线是活的、代表此刻"更明显(也与日历 App 的通行做法一致) */
.tg-now::before {
content: ""; position: absolute; left: -4px; top: -5px;
width: 8px; height: 8px; border-radius: 50%; background: #E0453B;
box-shadow: 0 0 0 2px rgba(255, 255, 255, .9);
}
/* 过午夜(或跨天翻页)时线要能"走出去",不要卡在格子边界上闪 */
.tg-col { overflow: hidden; }
/* ============================================================
v24 第 20 批 第 7 条:重复日程删除的**范围选择**
(只删这一次 / 这一次及以后 / 全部)
⚠️ 三档要**竖着列**并带说明文字:这是不可逆操作,横排挤成一行会看不清后果。
============================================================ */
.ev-del-scope { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.ev-del-head { margin: 0; font-size: 14px; color: var(--ink); }
.ev-del-picks { display: flex; flex-direction: column; gap: 6px; }
.ev-del-picks .chk-inline { align-items: flex-start; }
/* v24 第 20 批 第 8 条:提醒多选的排版(勾选框多了要能换行,别挤成一长条) */
.rem-picks { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.rem-pick { font-size: 13px; }