第九轮(部分):修复日报徽章配色、彻底重做PIN定位方案、清理静态文件品牌残留

- 日报卡片"已提交/未提交"状态徽章补色:DailyReportCard用<article><header><em>结构,原选择器
  要求.report-cards直接子<button>,日报卡片永远命中不了、只落到不分状态的兜底样式;月报因为
  行本身就是<button>所以没受影响。详情页.report-author em同理补了区分色(原来恒为绿色,"未
  提交"用绿色在语义上很怪)。
- 青少年模式/独立密码设置的PIN输入定位改用结构性方案,不再靠猜一个rem偏移量:给
  .auth-pin-boxes/.auth-pin-input包一层.auth-pin-wrap,输入框inset:0精确覆盖方块的真实
  渲染框,不管外层页面有没有标题、padding多少都自动对齐。上一轮的补丁(.youth-password-gate
  .auth-pin-input{top:1.75rem})把重叠率从0%提到32.5%,本轮实测确认还是不够(方块下方
  67.5%的面积点不中),根治后4个采样点(四角+中心)全部精确命中输入框。过程中额外发现并
  修复了两个更隐蔽的层叠问题:全局input[type=text]的height:2.5rem特异度反超裸class、
  textarea/input共享的margin:0.25rem重置——不清掉这两个会让inset:0看起来生效了、实际尺寸/
  位置还是被悄悄改掉。
- .report-loading(举报原因页加载态)补基础样式,原来在base.css里零匹配,吃浏览器默认<p>样式。
- 清理静态文件里的Tinode品牌残留:index.html/index-dev.html的<meta>标签(application-name/
  description/og:*/canonical,此前只改了<title>,meta标签一直没跟上)、manifest.json(name/
  short_name/description改MeetingIM;prefer_related_applications+related_applications整个
  删掉——这两个字段会让Android/Chrome"添加到主屏幕"时引导用户去装无关的第三方开源Tinode App,
  不只是文案问题);删除.well-known/assetlinks.json(声明了无关第三方App对本域名的URL处理/
  登录凭据代理权限,属于遗留模板残留,无真实对应关系,直接移除而非编造虚假内容)。

manifest.json里的图标文件(logo96.png/logo192.png/logo.svg)本身画的就是Tinode的蓝绿橙"t"标志,
是真实的视觉品牌泄漏,但重新设计一套图标涉及视觉识别决策,本次未动,留给用户决定。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-05 18:17:48 +08:00
co-authored by Claude Sonnet 5
parent f688578714
commit d573b38d10
10 changed files with 110 additions and 107 deletions
-26
View File
@@ -1,26 +0,0 @@
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "co.tinode.tindroidx",
"sha256_cert_fingerprints":
["25:9A:71:EE:BD:2E:9F:D3:2D:64:1E:90:49:33:BD:F3:E3:42:BD:C4:DD:FA:BA:68:EE:D2:EB:76:65:33:DC:92"]
}
},
{
"relation": ["delegate_permission/common.get_login_creds"],
"target": {
"namespace": "web",
"site": "https://web.tinode.co"
}
},
{
"relation": ["delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "co.tinode.tindroidx",
"sha256_cert_fingerprints":
["5B:BE:6A:C4:A7:66:A8:BB:4B:C1:52:66:E5:A4:BD:66:3C:33:50:04:11:CB:17:44:45:89:5E:3C:8D:55:77:7A"]
}
}
]
+43 -13
View File
@@ -1099,6 +1099,13 @@
font-style: normal;
}
/* DailyReportCard renders <article><header><em>, not the <button> the two rules below (1196-1197)
assume -- so submitted/draft there always fell through to this rule's one fixed color, unable to
tell the two states apart (MonthlyReportList's own rows really are <button>s, so it never hit
this gap). Same colors as the button-based rules, just re-declared at matching structure. */
.report-cards article > header em.submitted { color: #28784f; background: #e5f5ec; }
.report-cards article > header em.draft { color: #ad6c11; background: #fff2dd; }
.report-cards article p {
display: -webkit-box;
margin: 0.8rem 0;
@@ -1288,10 +1295,14 @@
}
.report-author em {
color: #2c8257;
font-style: normal;
}
/* Was one fixed color (green) regardless of report.status's actual value -- a "未提交" (not yet
submitted) badge showing the same green as "已提交" reads backwards. */
.report-author em.submitted { color: #28784f; }
.report-author em.draft { color: #ad6c11; }
.report-detail h2,
.report-detail h3,
.report-detail p {
@@ -6795,6 +6806,20 @@ div.toast.show {
margin-top: 1.2rem;
}
/* .auth-pin-input used to be sized/positioned with hardcoded top/width/height values guessed to
match wherever .auth-pin-boxes happened to render on each page -- .auth-pin-content's <h1> and
.youth-password-gate's shorter top padding push the boxes to two different heights, so one
shared guessed `top` could only ever be right for one of them (confirmed live twice: the two
pages' real box positions differ by ~9.25rem, and a value tuned for one left the other's boxes
only ~32% overlapping the real input, still mostly unclickable). Wrapping both in their own
tightly-fitting, positioned container instead -- inset:0 on the input then always matches
.auth-pin-boxes' actual rendered box exactly, on any page, with no measured constant to keep
in sync. */
.auth-pin-wrap {
position: relative;
display: inline-block;
}
.auth-pin-boxes {
display: grid;
grid-template-columns: repeat(4, 4rem);
@@ -6812,21 +6837,19 @@ div.toast.show {
.auth-pin-input {
position: absolute;
top: 11rem;
width: 19rem;
height: 5rem;
inset: 0;
margin: 0;
opacity: 0;
}
/* .auth-pin-input's shared top:11rem was measured against .auth-pin-content's layout (6rem
top padding + an <h1> pushing .auth-pin-boxes further down) -- .youth-password-gate has no
title and only 3rem of top padding, so .auth-pin-boxes sits ~9.25rem higher here and the
invisible real input ended up entirely below the visible boxes (confirmed live: boxes at
86.7-140px, input at 210-243px, a real click on the boxes hit nothing and never refocused
the input). Re-anchored to this container's own shorter layout instead of reusing the other
page's absolute value. */
.youth-password-gate .auth-pin-input {
top: 1.75rem;
/* input[type="text"]'s global reset (2131 above) sets a fixed height:2.5rem at higher specificity
(0,1,1) than a bare .auth-pin-input's (0,1,0) -- it was winning and pinning this input to 2.5rem
tall regardless of the boxes' actual height, undoing the inset:0 fix above for the vertical axis
(width happened to still come out right only because the reset's own width:100% agrees with this
element's actual need). Selector bumped to definitively outrank it, not just happen to. */
.auth-pin-wrap .auth-pin-input {
width: 100%;
height: 100%;
}
.auth-pin-content .auth-primary {
@@ -14061,6 +14084,13 @@ div.toast.show {
font-weight: 500;
}
.report-loading {
margin: 0;
padding: 1rem;
color: #6e7781;
font-size: 0.85rem;
}
.report-reason-list {
background: #fff;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+8 -8
View File
@@ -5,24 +5,24 @@
<title>MeetingIM</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="application-name" content="Tinode Web, version for developers" />
<meta name="description" content="Tinode instant messenger, development version" />
<meta name="application-url" content="https://web.tinode.co/">
<meta name="application-name" content="MeetingIM, version for developers" />
<meta name="description" content="MeetingIM instant messenger, development version" />
<meta name="application-url" content="https://web.118im.com/">
<!-- Browser styling for Chrome, FF, Opera -->
<meta name="theme-color" content="#3949AB" />
<!-- Ask Google not to offer page translation -->
<meta name="google" value="notranslate" />
<!-- Open Graph description -->
<meta name="og:url" content="https://web.tinode.co/">
<meta name="og:title" content="Tinode Web Development">
<meta name="og:url" content="https://web.118im.com/">
<meta name="og:title" content="MeetingIM Development">
<meta name="og:image" content="img/og-logo.jpeg">
<meta name="og:description" content="Tinode instant messenger, development version" />
<meta name="og:description" content="MeetingIM instant messenger, development version" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<link rel="manifest" href="manifest.json" />
<link rel="shortcut icon" id="shortcut-icon" href="img/logo32x32.png" type="image/png" />
<link rel="apple-touch-icon" sizes="192x192" href="img/logo192.png" type="image/png" />
<link rel="canonical" href="https://web.tinode.co/" />
<link rel="canonical" href="https://web.118im.com/" />
<!-- Google's Roboto font -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,500,700,700i" />
<!-- Google's material design icons -->
@@ -43,7 +43,7 @@
<body>
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
<div id="noscript-content">It appears JavaScript is turned off or is not supported by your browser.
TinodeWeb does not work without JavaScript. Please enable JavaScript by changing your browser options,
MeetingIM does not work without JavaScript. Please enable JavaScript by changing your browser options,
then <a href="">try again</a>.
</div>
</div></noscript>
+8 -8
View File
@@ -4,22 +4,22 @@
<title>MeetingIM</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="application-name" content="Tinode Web" />
<meta name="description" content="Tinode instant messenger in a browser" />
<meta name="application-url" content="https://web.tinode.co/">
<meta name="application-name" content="MeetingIM" />
<meta name="description" content="MeetingIM instant messenger in a browser" />
<meta name="application-url" content="https://web.118im.com/">
<meta name="theme-color" content="#3949AB" />
<meta name="google" value="notranslate" />
<meta name="format-detection" content="telephone=no" />
<meta name="og:url" content="https://web.tinode.co/">
<meta name="og:title" content="Tinode Web">
<meta name="og:url" content="https://web.118im.com/">
<meta name="og:title" content="MeetingIM">
<meta name="og:image" content="img/og-logo.jpeg">
<meta name="og:description" content="Tinode instant messenger in a browser" />
<meta name="og:description" content="MeetingIM instant messenger in a browser" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<link rel="manifest" href="manifest.json" />
<link rel="shortcut icon" id="shortcut-icon" href="img/logo32x32.png" type="image/png" />
<link rel="apple-touch-icon" sizes="192x192" href="img/logo192.png" type="image/png" />
<link rel="canonical" href="https://web.tinode.co/" />
<link rel="canonical" href="https://web.118im.com/" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,500,700,700i" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="css/base.min.css" />
@@ -32,7 +32,7 @@
<body>
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
<div id="noscript-content">It appears JavaScript is turned off or is not supported by your browser.
TinodeWeb does not work without JavaScript. Please enable JavaScript by changing your browser options,
MeetingIM does not work without JavaScript. Please enable JavaScript by changing your browser options,
then <a href="">try again</a>.</div></div></noscript>
<div id="mountPoint"></div>
<script src="firebase-init.js"></script>
+4 -15
View File
@@ -1,7 +1,7 @@
{
"name": "Tinode",
"short_name": "Tinode",
"description": "Tinode Web App.\nSee more at https://tinode.co/",
"name": "MeetingIM",
"short_name": "MeetingIM",
"description": "MeetingIM instant messenger.",
"categories": ["chat", "communication", "productivity"],
"icons": [
{
@@ -25,18 +25,7 @@
"theme_color": "#3949AB",
"display": "standalone",
"gcm_sender_id": "103953800507",
"prefer_related_applications": true,
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=co.tinode.tindroidx",
"id": "co.tinode.tindroidx"
},
{
"platform": "itunes",
"url": "https://apps.apple.com/us/app/tinode/id1483763538"
}
],
"prefer_related_applications": false,
"permissions": {
"audio-capture": {
"description": "Required for recording voice messages"
+4 -2
View File
@@ -765,8 +765,10 @@ function IndependentPassword({authenticated, hashParams}) {
<Header title="" back={authenticated ? 'security' : 'login'} />
<main className="auth-pin-content">
<h1>设置独立密码</h1>
<div className="auth-pin-boxes" aria-hidden="true">{[0,1,2,3].map(i => <span key={i}>{pin[i] ? '●' : ''}</span>)}</div>
<input className="auth-pin-input" type="text" value={pin} onChange={e => setPin(e.target.value.replace(/\D/g, '').slice(0, 4))} inputMode="numeric" autoComplete="off" aria-label="四位独立密码" autoFocus />
<div className="auth-pin-wrap">
<div className="auth-pin-boxes" aria-hidden="true">{[0,1,2,3].map(i => <span key={i}>{pin[i] ? '●' : ''}</span>)}</div>
<input className="auth-pin-input" type="text" value={pin} onChange={e => setPin(e.target.value.replace(/\D/g, '').slice(0, 4))} inputMode="numeric" autoComplete="off" aria-label="四位独立密码" autoFocus />
</div>
{error ? <div className="auth-field-error">{error}</div> : null}
<button className="auth-primary" disabled={pin.length !== 4 || busy} onClick={submit}>{busy ? '提交中…' : '确定'}</button>
</main>
+5 -3
View File
@@ -705,9 +705,11 @@ function YouthModeVerify({hashParams, onToast}) {
return <div className="personal-page youth-mode-page">
<Header title={mode === 'enable' ? '开启青少年模式' : '关闭青少年模式'} back={mode === 'enable' ? 'youth-mode-off' : 'youth-mode-on'} />
<main className="youth-password-gate">
<div className="auth-pin-boxes" aria-hidden="true">{[0, 1, 2, 3].map(i => <span key={i}>{password[i] ? '●' : ''}</span>)}</div>
<input className="auth-pin-input" type="text" inputMode="numeric" autoComplete="off" autoFocus
value={password} onChange={e => setPassword(e.target.value.replace(/\D/g, '').slice(0, 4))} aria-label="独立密码" />
<div className="auth-pin-wrap">
<div className="auth-pin-boxes" aria-hidden="true">{[0, 1, 2, 3].map(i => <span key={i}>{password[i] ? '●' : ''}</span>)}</div>
<input className="auth-pin-input" type="text" inputMode="numeric" autoComplete="off" autoFocus
value={password} onChange={e => setPassword(e.target.value.replace(/\D/g, '').slice(0, 4))} aria-label="独立密码" />
</div>
{error ? <div className="auth-field-error">{error}</div> : null}
<button type="button" className="auth-primary" disabled={password.length !== 4 || busy || remainingLockSeconds > 0} onClick={submit}>
{busy ? '验证中…' : remainingLockSeconds > 0 ? `请 ${remainingLockSeconds} 秒后重试` : '确定'}
+36 -30
View File
@@ -6817,21 +6817,24 @@ function IndependentPassword({
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("h1", {
children: "设置独立密码"
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "auth-pin-boxes",
"aria-hidden": "true",
children: [0, 1, 2, 3].map(i => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("span", {
children: pin[i] ? '●' : ''
}, i, false))
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("input", {
className: "auth-pin-input",
type: "text",
value: pin,
onChange: e => setPin(e.target.value.replace(/\D/g, '').slice(0, 4)),
inputMode: "numeric",
autoComplete: "off",
"aria-label": "四位独立密码",
autoFocus: true
}, void 0, false), error ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "auth-pin-wrap",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "auth-pin-boxes",
"aria-hidden": "true",
children: [0, 1, 2, 3].map(i => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("span", {
children: pin[i] ? '●' : ''
}, i, false))
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("input", {
className: "auth-pin-input",
type: "text",
value: pin,
onChange: e => setPin(e.target.value.replace(/\D/g, '').slice(0, 4)),
inputMode: "numeric",
autoComplete: "off",
"aria-label": "四位独立密码",
autoFocus: true
}, void 0, false)]
}, void 0, true), error ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("div", {
className: "auth-field-error",
children: error
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_3__.jsxDEV)("button", {
@@ -16197,21 +16200,24 @@ function YouthModeVerify({
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("main", {
className: "youth-password-gate",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
className: "auth-pin-boxes",
"aria-hidden": "true",
children: [0, 1, 2, 3].map(i => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("span", {
children: password[i] ? '●' : ''
}, i, false))
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("input", {
className: "auth-pin-input",
type: "text",
inputMode: "numeric",
autoComplete: "off",
autoFocus: true,
value: password,
onChange: e => setPassword(e.target.value.replace(/\D/g, '').slice(0, 4)),
"aria-label": "独立密码"
}, void 0, false), error ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
className: "auth-pin-wrap",
children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
className: "auth-pin-boxes",
"aria-hidden": "true",
children: [0, 1, 2, 3].map(i => (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("span", {
children: password[i] ? '●' : ''
}, i, false))
}, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("input", {
className: "auth-pin-input",
type: "text",
inputMode: "numeric",
autoComplete: "off",
autoFocus: true,
value: password,
onChange: e => setPassword(e.target.value.replace(/\D/g, '').slice(0, 4)),
"aria-label": "独立密码"
}, void 0, false)]
}, void 0, true), error ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("div", {
className: "auth-field-error",
children: error
}, void 0, false) : null, (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_7__.jsxDEV)("button", {
+1 -1
View File
File diff suppressed because one or more lines are too long