feat(ui): 星盘 tab 下划线化、参数收成键值表,星历日期条改 ‹ 日期 ›
星盘页(T5.1): - 五个体系 tab 从五枚描边药丸改成顶栏下方一条下划线式 tab 行, flex-wrap: nowrap + 容器自身横滚,375px 下仍是一行;底线画在滚动 容器的 border 上,不随内容滚走。眉标(BUG-717 的成本与速度承诺) 原文保留,位置从 tab 行上方移到下方,让 tab 行直接接住 46px 顶栏。 - 分盘 chips 保留但降一级:去描边去卡面、caption 字号、三级色, 当前项只留浅底。它是一个 tab 内部的筛选器,不是第二层导航。 - 参数从「中宫叠卡(手机两行/桌面六行)+ 盘下重复卡 + media query 三选一」收成一张八行键值表。同一组事实原本画三遍,改一处要改三处。 .chart-page-center-card / -compact / -extra / .chart-page-params-below / .chart-page-boundary-desktop 五个类连同 767/768 开关块一并删除。 view.vedic.boundary 那句准确性边界句原文未动,紧跟在表下方,且 DOM 里从出现两次变成一次。 - 三张行星表(印度宿度 / 西洋相位 / 七政十一曜)全部包上横向滚动 容器,wrap 补 min-width: 0 —— 少了它 grid item 按内容定宽,表格会 撑宽页面而不是在自己盒子里滚动。原先只有第一张有 wrap。 星历页(T5.2): - 日期导航从三个并排等宽按钮改成居中的 ‹ 日期 ›:日期是主语,两枚 44px 箭头是把手。箭头字形 aria-hidden,可访问名仍是「前一天」 「后一天」,读屏不丢方向。 - 「今天」只在非今日时以文字按钮出现在日期下方;在今日时整个不渲染, 而不是渲染成禁用按钮——灰掉的按钮仍要被读、仍要被 Tab 跳过。 - 三段统一成同一套卡片:框从内容上移到段落,.ephemeris-panchanga 的 dl 网格原样保留。收尾句原文不变,但从底部第四张卡降为页尾一行小字。 两条既有断言改动,测试文件内就地写了原值/新值/原因,进度记录成表: 参数断点开关的三条断言换成「参数只有一张键值表且那五个类不再有规则」 (更强,且避开了原写法 indexOf 未命中后静默锚错断点块的陷阱); 「今天」在场的单状态断言拆成今日/非今日两状态并加断不出现 disabled。 测试总数 3350 → 3352,失败清单与基线逐条一致(31 条无 Docker 缺口)。 tsc 0 错;lint 0 error / 118 warning(与基线同数);/ /chart /ephemeris 渲染标记均保持 ○ Static,/reports 仍 ƒ;主 CSS gzip 36,745 → 36,788 (+0.12%)。行星表 375px 横滚、tab 行横滚、读屏播报无真机验收,已在 进度记录里如实写成环境缺口。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
co-authored by
Claude Opus 5
parent
ca0204984b
commit
1e3570b462
@@ -74,10 +74,6 @@ export function ChartPageView({
|
||||
</section>
|
||||
) : (
|
||||
<div className="chart-page-body">
|
||||
{/* BUG-717 copy: a cost and speed promise that also prints on the
|
||||
failure page. It used to sit in the hero as an eyebrow; the hero is
|
||||
gone, the promise is not. */}
|
||||
<p className="chart-page-eyebrow">{CHART_VIEW_COPY.eyebrow}</p>
|
||||
<div className="chart-page-tabs" role="tablist" aria-label="星盘体系">
|
||||
{CHART_VIEW_TABS.map((item) => (
|
||||
<button
|
||||
@@ -92,6 +88,11 @@ export function ChartPageView({
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* BUG-717 copy: a cost and speed promise that also prints on the
|
||||
failure page. It used to sit in the hero as an eyebrow; the hero is
|
||||
gone, the promise is not. It reads under the tab row now, because
|
||||
the tab row is what the header hands off to. */}
|
||||
<p className="chart-page-eyebrow">{CHART_VIEW_COPY.eyebrow}</p>
|
||||
<section className="chart-page-panel" role="tabpanel">
|
||||
{tab === "vedic" ? (
|
||||
<ChartVedicTab
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import type { ChartViewOk } from "@/lib/chart-view-contract";
|
||||
import { formatCoord } from "./chart-format";
|
||||
|
||||
export function ChartParamLines({
|
||||
view,
|
||||
compact,
|
||||
}: {
|
||||
view: ChartViewOk;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const { profile } = view;
|
||||
const place = `${profile.placeLabel} · ${formatCoord(profile.latitude, profile.longitude)}`;
|
||||
if (compact) {
|
||||
return (
|
||||
<>
|
||||
<p>岁差 {profile.ayanamsaDisplay}</p>
|
||||
<p>{profile.nodeModeLabel}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<p>{profile.time} · {profile.birthTimeStatusLabel}</p>
|
||||
<p>{place}</p>
|
||||
<p>岁差 {profile.ayanamsaDisplay}</p>
|
||||
<p>{profile.nodeModeLabel}</p>
|
||||
<p>升 {profile.ascendantSignLabel} · 月宿 {profile.moonNakshatra} 第{profile.moonPada}足</p>
|
||||
<p>{profile.currentDashaLabel} · {profile.engineName}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { ChartViewOk } from "@/lib/chart-view-contract";
|
||||
import { formatCoord } from "./chart-format";
|
||||
|
||||
/**
|
||||
* The chart parameters as one key/value table.
|
||||
*
|
||||
* They used to be rendered three times over: a `.chart-page-center-card`
|
||||
* floating on the geometric centre of the North Indian chart carried two lines
|
||||
* on mobile and six on desktop, and a `.chart-page-params-below` card repeated
|
||||
* the same six lines for mobile only. One set of facts, three renders, switched
|
||||
* by media queries — and every line was a bare sentence with its label fused to
|
||||
* its value (「岁差 Raman」). A table names each field once and scans down one
|
||||
* column, so nothing needs duplicating per breakpoint.
|
||||
*
|
||||
* Every value here is passed through from the server projection; nothing on
|
||||
* this page is computed in the browser.
|
||||
*/
|
||||
export function ChartParamTable({ view }: { view: ChartViewOk }) {
|
||||
const { profile } = view;
|
||||
const rows: ReadonlyArray<readonly [string, string]> = [
|
||||
["出生时间", `${profile.time} · ${profile.birthTimeStatusLabel}`],
|
||||
["出生地", `${profile.placeLabel} · ${formatCoord(profile.latitude, profile.longitude)}`],
|
||||
["岁差", profile.ayanamsaDisplay],
|
||||
["交点", profile.nodeModeLabel],
|
||||
["上升", profile.ascendantSignLabel],
|
||||
["月宿", `${profile.moonNakshatra} 第${profile.moonPada}足`],
|
||||
["当前大运", profile.currentDashaLabel],
|
||||
["引擎", profile.engineName],
|
||||
];
|
||||
return (
|
||||
<dl className="chart-page-params-table">
|
||||
{rows.map(([label, value]) => (
|
||||
<div className="chart-page-param-row" key={label}>
|
||||
<dt>{label}</dt>
|
||||
<dd>{value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
@@ -27,29 +27,31 @@ export function ChartQizhengTab({ view }: { view: ChartViewOk }) {
|
||||
<p className="chart-page-note">计都派别:{qizheng.ketuMode} · 坐标系 {qizheng.coordinateSystem}</p>
|
||||
<Rules />
|
||||
<QizhengPalaceGrid qizheng={qizheng} />
|
||||
<table className="chart-page-planet-table">
|
||||
<caption>十一曜宿度</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">曜</th>
|
||||
<th scope="col">宿</th>
|
||||
<th scope="col">宿度</th>
|
||||
<th scope="col">宫</th>
|
||||
<th scope="col">庙旺</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{qizheng.bodies.map((body) => (
|
||||
<tr key={body.name}>
|
||||
<th scope="row">{body.name}</th>
|
||||
<td>{body.mansion}</td>
|
||||
<td>{body.mansionDegree.toFixed(2)}</td>
|
||||
<td>{body.palace}</td>
|
||||
<td>{body.dignityClosed ? body.dignity : `${body.dignity}(未闭合)`}</td>
|
||||
<div className="chart-page-planet-table-wrap">
|
||||
<table className="chart-page-planet-table">
|
||||
<caption>十一曜宿度</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">曜</th>
|
||||
<th scope="col">宿</th>
|
||||
<th scope="col">宿度</th>
|
||||
<th scope="col">宫</th>
|
||||
<th scope="col">庙旺</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{qizheng.bodies.map((body) => (
|
||||
<tr key={body.name}>
|
||||
<th scope="row">{body.name}</th>
|
||||
<td>{body.mansion}</td>
|
||||
<td>{body.mansionDegree.toFixed(2)}</td>
|
||||
<td>{body.palace}</td>
|
||||
<td>{body.dignityClosed ? body.dignity : `${body.dignity}(未闭合)`}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { VedicChartSvg } from "@/components/personal-report/vedic-chart-svg";
|
||||
import type { ChartViewOk } from "@/lib/chart-view-contract";
|
||||
import { CHART_VIEW_COPY, VARGA_CHIP_ORDER } from "@/lib/chart-view-labels";
|
||||
import { formatDegree } from "./chart-format";
|
||||
import { ChartParamLines } from "./chart-param-lines";
|
||||
import { ChartParamTable } from "./chart-param-table";
|
||||
|
||||
export function ChartVedicTab({
|
||||
view,
|
||||
@@ -45,17 +45,12 @@ export function ChartVedicTab({
|
||||
<div className="chart-page-vedic-layout">
|
||||
<div className="chart-page-vedic-stage">
|
||||
<VedicChartSvg chart={varga.chart} ariaLabel={`${varga.id} 北印度盘 ${varga.meaning}`} />
|
||||
<div className="chart-page-center-card">
|
||||
<div className="chart-page-center-compact">
|
||||
<ChartParamLines view={view} compact />
|
||||
</div>
|
||||
<div className="chart-page-center-extra">
|
||||
<ChartParamLines view={view} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="chart-page-params-below">
|
||||
<ChartParamLines view={view} />
|
||||
<div className="chart-page-params">
|
||||
<ChartParamTable view={view} />
|
||||
{/* The accuracy boundary sits directly under the parameters it
|
||||
qualifies. Server copy, reproduced verbatim — it says what this
|
||||
chart can and cannot be read for. */}
|
||||
<p className="chart-page-boundary">{view.vedic.boundary}</p>
|
||||
</div>
|
||||
<div className="chart-page-planet-table-wrap">
|
||||
@@ -83,7 +78,6 @@ export function ChartVedicTab({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{varga ? <p className="chart-page-boundary chart-page-boundary-desktop">{view.vedic.boundary}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,24 +26,26 @@ export function ChartWesternTab({ view }: { view: ChartViewOk }) {
|
||||
</section>
|
||||
<section>
|
||||
<h3>主要相位</h3>
|
||||
<table className="chart-page-planet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">星体</th>
|
||||
<th scope="col">相位</th>
|
||||
<th scope="col">容许度</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{western.aspects.slice(0, 12).map((aspect) => (
|
||||
<tr key={`${aspect.left}-${aspect.right}-${aspect.aspect}`}>
|
||||
<td>{aspect.left} · {aspect.right}</td>
|
||||
<td>{aspect.aspectLabel}</td>
|
||||
<td>{aspect.orb.toFixed(1)}°</td>
|
||||
<div className="chart-page-planet-table-wrap">
|
||||
<table className="chart-page-planet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">星体</th>
|
||||
<th scope="col">相位</th>
|
||||
<th scope="col">容许度</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{western.aspects.slice(0, 12).map((aspect) => (
|
||||
<tr key={`${aspect.left}-${aspect.right}-${aspect.aspect}`}>
|
||||
<td>{aspect.left} · {aspect.right}</td>
|
||||
<td>{aspect.aspectLabel}</td>
|
||||
<td>{aspect.orb.toFixed(1)}°</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<p>宫制 {western.houseSystem === "P" ? "Placidus" : western.houseSystem}</p>
|
||||
</div>
|
||||
|
||||
@@ -126,18 +126,42 @@ export function EphemerisView(props: {
|
||||
>
|
||||
<div className="ephemeris-body">
|
||||
<section className="ephemeris-hero">
|
||||
<p className="ephemeris-date-label">{props.date ? formatEphemerisDate(props.date) : ""}</p>
|
||||
{/* `‹ 日期 ›`: the date is the subject, the two arrows are its handles.
|
||||
Three equal side-by-side buttons made 前一天 / 今天 / 后一天 read as
|
||||
three peers and pushed the date itself out of the group entirely.
|
||||
The arrow glyphs are decorative — the direction words stay as the
|
||||
accessible names, so nothing is lost to a screen reader. */}
|
||||
<div className="ephemeris-date-bar" role="group" aria-label="切换日期">
|
||||
<Button type="button" variant="outline" className="ephemeris-date-button" onClick={props.onPrevious}>
|
||||
{EPHEMERIS_COPY.previousDay}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="ephemeris-date-button"
|
||||
aria-label={EPHEMERIS_COPY.previousDay}
|
||||
onClick={props.onPrevious}
|
||||
>
|
||||
<span aria-hidden="true">‹</span>
|
||||
</Button>
|
||||
<Button type="button" variant="outline" className="ephemeris-date-button" onClick={props.onToday} disabled={isToday}>
|
||||
{EPHEMERIS_COPY.today}
|
||||
</Button>
|
||||
<Button type="button" variant="outline" className="ephemeris-date-button" onClick={props.onNext}>
|
||||
{EPHEMERIS_COPY.nextDay}
|
||||
<p className="ephemeris-date-label">{props.date ? formatEphemerisDate(props.date) : ""}</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="ephemeris-date-button"
|
||||
aria-label={EPHEMERIS_COPY.nextDay}
|
||||
onClick={props.onNext}
|
||||
>
|
||||
<span aria-hidden="true">›</span>
|
||||
</Button>
|
||||
</div>
|
||||
{/* On today there is nowhere to go, so the control is absent rather than
|
||||
present-but-disabled: a greyed button still asks to be read and
|
||||
still has to be tabbed past. */}
|
||||
{isToday ? null : (
|
||||
<Button type="button" variant="ghost" className="ephemeris-today" onClick={props.onToday}>
|
||||
{EPHEMERIS_COPY.today}
|
||||
</Button>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="ephemeris-section" aria-labelledby="ephemeris-panchanga-heading">
|
||||
@@ -203,9 +227,10 @@ export function EphemerisView(props: {
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="ephemeris-footer">
|
||||
<p>{EPHEMERIS_COPY.footerNote}</p>
|
||||
</section>
|
||||
{/* The closing note used to be a fourth boxed card competing with the
|
||||
three that carry data. The sentence is unchanged; it is a quiet line
|
||||
under the page now, and the 「带这天去提问」 action lives in the header. */}
|
||||
<p className="ephemeris-outro">{EPHEMERIS_COPY.footerNote}</p>
|
||||
</div>
|
||||
</SecondaryShell>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user