fix(frontend): style the paywall header and guard undefined class names
The onboarding paywall wrote `dialog-header`, a class no stylesheet defines, so its close button stacked under the title instead of sitting beside it. The session-delete confirm buttons had the same problem, and a `message-entry` wrapper added in August had quietly killed the `.message + .message` spacing rule in the main chat. Fixes all three against the patterns already in the codebase, then adds a contract test so the next undefined class fails instead of shipping: it scopes itself to our own class families so Tailwind utilities stay out, strips CSS comments before deciding what counts as defined, and carries an allowlist of the fifteen deliberate no-op modifiers that must shrink rather than grow. Also collapses the paywall's duplicate display heading into the intro sentence, leaving one heading in the dialog. BUG-434, BUG-435, BUG-436. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
This commit is contained in:
@@ -1411,8 +1411,8 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.paywall-intro { display: flex; align-items: flex-start; gap: var(--space-4); padding-bottom: var(--space-5); }
|
||||
.paywall-mark { width: 44px; height: 44px; display: grid; flex: 0 0 auto; place-items: center; border-radius: 50%; background: var(--color-action-soft); color: var(--color-action); }
|
||||
.paywall-mark svg { width: 20px; height: 20px; }
|
||||
.paywall-intro b { display: block; color: var(--color-ink); font-family: var(--font-display); font-size: var(--type-title-md); font-weight: 400; }
|
||||
.paywall-intro p { margin: var(--space-1) 0 0; color: var(--color-ink-secondary); font-size: var(--type-body-sm); line-height: 1.6; text-wrap: pretty; }
|
||||
.paywall-intro b { color: var(--color-ink); font-weight: 600; }
|
||||
.paywall-intro p { margin: 0; color: var(--color-ink-secondary); font-size: var(--type-body-sm); line-height: 1.6; text-wrap: pretty; }
|
||||
.paywall-footer { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); margin-top: var(--space-6); padding-top: var(--space-5); border-top: 1px solid var(--color-border); }
|
||||
.paywall-footer span { color: var(--color-ink-secondary); font-size: var(--type-caption); }
|
||||
.paywall-footer > a, .paywall-footer > button { flex: 0 0 auto; text-decoration: none; }
|
||||
@@ -2086,7 +2086,10 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.conversation:not(.is-empty):not(.is-rectification) .message + .message {
|
||||
/* Each message renders inside its own .message-entry wrapper (row + actions +
|
||||
follow-ups), so two .message elements are never adjacent siblings. Space the
|
||||
wrappers instead. */
|
||||
.conversation:not(.is-empty):not(.is-rectification) .message-entry + .message-entry {
|
||||
margin-top: var(--space-8);
|
||||
}
|
||||
|
||||
@@ -2177,7 +2180,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
padding-bottom: var(--space-16);
|
||||
}
|
||||
|
||||
.conversation:not(.is-empty):not(.is-rectification) .message + .message {
|
||||
.conversation:not(.is-empty):not(.is-rectification) .message-entry + .message-entry {
|
||||
margin-top: var(--space-6);
|
||||
}
|
||||
|
||||
|
||||
@@ -3873,7 +3873,7 @@ export default function Home() {
|
||||
</select>
|
||||
<button className="button-secondary" type="button" onClick={() => void draftSynastryQuestionFromChart(record, synastryRelationshipType)} disabled={synastryPendingId !== null}>{synastryPendingId === record.id ? "正在计算合盘..." : "用于合盘"}</button>
|
||||
<button className="button-secondary" type="button" onClick={() => void makeDefaultChart(record)} disabled={profileSaving}>设为默认</button>
|
||||
<button className="button-secondary danger-button" type="button" onClick={() => deleteOtherChart(record.id)}>删除</button>
|
||||
<button className="button-secondary" type="button" onClick={() => deleteOtherChart(record.id)}>删除</button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
@@ -3997,8 +3997,8 @@ export default function Home() {
|
||||
<h2>删除聊天记录?</h2>
|
||||
<p>“{pendingSessionDeletion.title}”将被永久删除,无法恢复。</p>
|
||||
<div className="dialog-actions">
|
||||
<button type="button" onClick={() => setPendingSessionDeletion(null)}>取消</button>
|
||||
<button className="danger-button" type="button" onClick={() => {
|
||||
<button className="button-secondary" type="button" onClick={() => setPendingSessionDeletion(null)}>取消</button>
|
||||
<button className="button-primary danger-primary" type="button" onClick={() => {
|
||||
const session = pendingSessionDeletion;
|
||||
setPendingSessionDeletion(null);
|
||||
void deleteSession(session);
|
||||
|
||||
@@ -84,18 +84,15 @@ export function OnboardingRedeemPaywall({
|
||||
else keepFocusWithin(event.nativeEvent, event.currentTarget);
|
||||
}}
|
||||
>
|
||||
<div className="dialog-header">
|
||||
<header className="account-modal-header">
|
||||
<h2 id="onboarding-paywall-title">解锁完整咨询</h2>
|
||||
<button className="dialog-close" aria-label="关闭" type="button" onClick={close} disabled={pending}>
|
||||
<X aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div className="paywall-intro">
|
||||
<span className="paywall-mark" aria-hidden="true"><Gift /></span>
|
||||
<div>
|
||||
<b>入门问题已经准备好</b>
|
||||
<p>填写兑换码,解锁后续咨询、生时校正与完整解读。</p>
|
||||
</div>
|
||||
<p><b>入门问题已经准备好。</b>填写兑换码,解锁后续咨询、生时校正与完整解读。</p>
|
||||
</div>
|
||||
<div className="redeem-balance">
|
||||
<span>当前可用点数</span>
|
||||
|
||||
Reference in New Issue
Block a user