diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index d86c3a5a..caf0c2ef 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -853,6 +853,24 @@ button:disabled { cursor: default; opacity: .45; }
.chat-header-chart { flex: 0 1 auto; min-width: 0; overflow: hidden; color: var(--color-ink-tertiary); font-size: var(--type-caption); text-overflow: ellipsis; white-space: nowrap; }
.chat-header-actions { min-width: max-content; display: flex; flex: 0 0 auto; align-items: center; gap: var(--space-2); white-space: nowrap; }
.chat-header-actions > * { flex: 0 0 auto; }
+/* The page header is 46px tall, so its actions are 32px — the button primitives
+ default to 44px, which is why 「生成完整报告」 read as a banner CTA parked in
+ the corner. The touch target comes back through the hit expansion below, the
+ same trade the message actions and the starter pills make. */
+.chat-header-actions button {
+ position: relative;
+ min-height: 32px;
+ height: 32px;
+ padding-inline: var(--space-3);
+ border-radius: var(--radius-md);
+ font-size: var(--type-caption);
+}
+.chat-header-actions button::after {
+ position: absolute;
+ inset: -6px;
+ content: "";
+}
+.chat-header-actions button > svg { width: 14px; height: 14px; }
.credit-button { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); cursor: pointer; font-size: var(--type-caption); font-variant-numeric: tabular-nums; transition: background-color 120ms ease-out, transform 120ms ease-out; min-width: 64px; border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas-soft); color: var(--color-ink-secondary); font-weight: 500; }
.conversation { min-width: 0; min-height: 0; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; padding-bottom: var(--composer-reserve); background: var(--color-canvas); }
@@ -861,6 +879,12 @@ button:disabled { cursor: default; opacity: .45; }
stack reads as one centred group while the composer keeps the single DOM
position it has in a live session. */
.conversation.is-empty { display: grid; align-content: end; justify-items: center; padding: var(--space-8) var(--space-8) 0; }
+/* With the balance row present the panel has four rows: header, the conversation
+ (1fr, greeting bottom-aligned), the composer (auto), and an equal 1fr below.
+ The two 1fr rows cancel out, so the group is vertically centred while the
+ composer keeps the single DOM position it holds in a live session. */
+.chat-panel:has(.starter-balance) { grid-template-rows: 46px minmax(0, 1fr) auto minmax(0, 1fr); }
+.starter-balance { min-height: 0; }
.welcome { width: min(820px, 100%); padding: var(--space-10) 0 var(--space-16); }
.welcome > .onboarding-message:first-child { padding-bottom: var(--space-8); }
.welcome > .onboarding-message:first-child .message-bubble p {
@@ -2208,7 +2232,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
.composer-wrap-starter {
border-top-color: transparent;
- padding-bottom: var(--space-16);
+ padding-bottom: var(--space-4);
background: transparent;
backdrop-filter: none;
}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index 3acc6e47..160445c7 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -1807,6 +1807,12 @@ export default function Home() {
/>
)}
}
+ {/* Balances the 1fr row above the composer so the greeting / composer /
+ pills group sits in the middle of the panel instead of resting on the
+ bottom edge. It collapses to 0 on short viewports. */}
+ {starterHomeVisible && !rectificationSurfaceOpen && !onboardingFormActive
+ ?
+ : null}
{title}
-
{method}
-
当前 {currentLabel}
+ {/* Both tracks ship a `method` equal to their own title, which rendered
+ the name twice; only show it when it actually adds something. */}
+ {method && method !== title ?
{method}
: null}
+ {/* No space, and no second 当前: the fallback labels used to start with
+ it themselves, which read as 「当前 当前 Chara 段…」. */}
+