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
+138
-92
@@ -1995,7 +1995,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
}
|
||||
.chart-page-eyebrow {
|
||||
width: min(1100px, 100%);
|
||||
margin: var(--space-4) auto 0;
|
||||
margin: 0 auto var(--space-6);
|
||||
color: var(--color-ink-tertiary);
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
@@ -2073,8 +2073,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
.report-center-body,
|
||||
.chart-page-message,
|
||||
.ephemeris-message,
|
||||
.report-center-message,
|
||||
.chart-page-eyebrow { padding-inline: var(--space-4); }
|
||||
.report-center-message { padding-inline: var(--space-4); }
|
||||
}
|
||||
|
||||
/* Starter home: greeting, composer, entry pills */
|
||||
@@ -4500,30 +4499,62 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
/* The five systems are one underline tab row directly under the app header,
|
||||
not five bordered pills floating on the page. Five pills of equal weight read
|
||||
as five buttons you might press; a tab row reads as one control with a
|
||||
current position. The row scrolls sideways rather than wrapping, so the
|
||||
header hand-off stays a single line at 375px — the border sits on the
|
||||
scroll container itself and therefore does not scroll away. */
|
||||
.chart-page-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-6);
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-4);
|
||||
padding-top: var(--space-2);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.chart-page-tab,
|
||||
.chart-page-chip {
|
||||
.chart-page-tabs::-webkit-scrollbar { display: none; }
|
||||
.chart-page-tab {
|
||||
flex: 0 0 auto;
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
padding: 0 var(--space-3);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-canvas);
|
||||
padding: 0 var(--space-1);
|
||||
border: 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: var(--color-ink-secondary);
|
||||
cursor: pointer;
|
||||
font-size: var(--type-body-sm);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chart-page-tab.is-current,
|
||||
.chart-page-tab:hover { color: var(--color-ink); }
|
||||
.chart-page-tab.is-current {
|
||||
border-bottom-color: var(--color-focus);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
/* Vargas are a filter inside one tab, not a second navigation level, so the
|
||||
chips sit a rung below the tab row: no frame, no card face, caption type. */
|
||||
.chart-page-chip {
|
||||
min-height: 44px;
|
||||
min-width: 36px;
|
||||
padding: 0 var(--space-2);
|
||||
border: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: var(--color-ink-tertiary);
|
||||
cursor: pointer;
|
||||
font-size: var(--type-caption);
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.chart-page-chip:hover { color: var(--color-ink-secondary); }
|
||||
.chart-page-chip.is-current {
|
||||
background: var(--color-selected);
|
||||
border-color: var(--color-border-strong);
|
||||
box-shadow: inset 2px 0 0 var(--color-focus);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.chart-page-vedic,
|
||||
@@ -4531,14 +4562,13 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
.chart-page-dasha,
|
||||
.chart-page-western,
|
||||
.chart-page-qizheng,
|
||||
.chart-page-western-side,
|
||||
.chart-page-center-compact {
|
||||
.chart-page-western-side {
|
||||
min-width: 0;
|
||||
}
|
||||
.chart-page-varga-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
gap: var(--space-1);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.chart-page-vedic-stage {
|
||||
@@ -4550,40 +4580,52 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
}
|
||||
.chart-page-vedic-stage .personal-report-chart-svg { margin-top: 0; }
|
||||
.chart-page-vedic-stage .personal-report-chart-svg text { font-size: 16px; }
|
||||
.chart-page-center-card {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: min(42%, 168px);
|
||||
padding: var(--space-2);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in srgb, var(--color-canvas) 92%, transparent);
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-overline);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.35;
|
||||
text-align: center;
|
||||
}
|
||||
.chart-page-center-card p { margin: 0; }
|
||||
.chart-page-center-extra { display: none; }
|
||||
.chart-page-params-below {
|
||||
display: none;
|
||||
margin-top: var(--space-4);
|
||||
/* One key/value table replaces the `.chart-page-center-card` overlay (two lines
|
||||
on mobile, six on desktop, sat on the chart's geometric centre) and the
|
||||
`.chart-page-params-below` card that repeated the same six lines for mobile.
|
||||
The same facts were laid out three ways and switched by media query; naming
|
||||
each field once removes the per-breakpoint duplication entirely. */
|
||||
.chart-page-params {
|
||||
min-width: 0;
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.chart-page-params-below p { margin: 0 0 var(--space-2); }
|
||||
.chart-page-planet-table-wrap { overflow-x: auto; }
|
||||
.chart-page-params-table { margin: 0; }
|
||||
.chart-page-param-row {
|
||||
display: grid;
|
||||
grid-template-columns: 5.5rem minmax(0, 1fr);
|
||||
gap: var(--space-3);
|
||||
align-items: baseline;
|
||||
padding: var(--space-2) 0;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
.chart-page-param-row:first-child { border-top: 0; padding-top: 0; }
|
||||
.chart-page-param-row dt {
|
||||
margin: 0;
|
||||
color: var(--color-ink-tertiary);
|
||||
font-size: var(--type-caption);
|
||||
font-weight: 500;
|
||||
}
|
||||
.chart-page-param-row dd {
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
font-size: var(--type-body-sm);
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
/* `min-width: 0` matters as much as the overflow: without it a grid item is
|
||||
sized by its content and the table pushes the page sideways instead of
|
||||
scrolling inside this box. */
|
||||
.chart-page-planet-table-wrap {
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
}
|
||||
.chart-page-planet-table {
|
||||
width: 100%;
|
||||
min-width: 26rem;
|
||||
border-collapse: collapse;
|
||||
background: var(--color-canvas);
|
||||
font-size: var(--type-body-sm);
|
||||
@@ -4710,17 +4752,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
}
|
||||
.chart-page-unavailable > p { margin: 0 0 var(--space-3); font-size: var(--type-body-md); }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.chart-page-center-compact { display: block; }
|
||||
.chart-page-center-extra { display: none; }
|
||||
.chart-page-params-below { display: block; }
|
||||
.chart-page-boundary-desktop { display: none; }
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.chart-page-center-compact { display: none; }
|
||||
.chart-page-center-extra { display: block; }
|
||||
.chart-page-params-below { display: none; }
|
||||
.chart-page-basics-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
@@ -4732,6 +4764,9 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
gap: var(--space-6);
|
||||
align-items: start;
|
||||
}
|
||||
/* Chart and parameters sit side by side; the planet table is a full-width
|
||||
row under both, because its columns need the whole measure. */
|
||||
.chart-page-planet-table-wrap { grid-column: 1 / -1; }
|
||||
.chart-page-basics-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
@@ -4749,34 +4784,55 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
Ephemeris page — facts only, own scroll (html/body overflow hidden)
|
||||
============================================================ */.ephemeris-hero,
|
||||
.ephemeris-section,
|
||||
.ephemeris-footer{
|
||||
.ephemeris-outro{
|
||||
width: min(900px, 100%);
|
||||
margin-inline: auto;
|
||||
}
|
||||
.ephemeris-hero {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
justify-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-6) 0 var(--space-8);
|
||||
}
|
||||
.ephemeris-date-label {
|
||||
margin: 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-title-sm);
|
||||
min-width: 12ch;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--type-title-md);
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
.ephemeris-date-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
.ephemeris-date-button {
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
font-size: var(--type-title-md);
|
||||
line-height: 1;
|
||||
}
|
||||
/* Only rendered off today, so it never needs a disabled face. */
|
||||
.ephemeris-today {
|
||||
min-height: 44px;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
}
|
||||
/* One card per section. The three bodies used to draw their own frames while
|
||||
the section around them drew none, so 五要素 / 行运 / 未来九十天 each looked
|
||||
like a different kind of object. The frame belongs to the section; what is
|
||||
inside it is rows. */
|
||||
.ephemeris-section {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-10);
|
||||
margin-bottom: var(--space-6);
|
||||
padding: var(--space-5);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
.ephemeris-section h2 {
|
||||
margin: 0;
|
||||
@@ -4806,10 +4862,10 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
margin: 0;
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas);
|
||||
padding: var(--space-3);
|
||||
border: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-canvas-muted);
|
||||
}
|
||||
.ephemeris-panchanga-item dt {
|
||||
margin: 0;
|
||||
@@ -4834,9 +4890,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
}
|
||||
.ephemeris-transits {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.ephemeris-transits-head,
|
||||
.ephemeris-transits-row {
|
||||
@@ -4844,11 +4898,11 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
grid-template-columns: 4.5rem minmax(0, 1fr) 5rem 6.5rem 3rem;
|
||||
gap: var(--space-3);
|
||||
align-items: center;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
padding: var(--space-3) 0;
|
||||
min-height: 44px;
|
||||
}
|
||||
.ephemeris-transits-head {
|
||||
background: var(--color-canvas-muted);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
color: var(--color-ink-tertiary);
|
||||
font-size: var(--type-caption);
|
||||
font-weight: 500;
|
||||
@@ -4865,9 +4919,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.ephemeris-events li {
|
||||
display: grid;
|
||||
@@ -4875,7 +4927,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
gap: var(--space-4);
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
padding: var(--space-3) 0;
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-size: var(--type-body-sm);
|
||||
}
|
||||
@@ -4884,17 +4936,10 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
color: var(--color-ink-tertiary);
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
.ephemeris-footer {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-6);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
.ephemeris-footer p {
|
||||
.ephemeris-outro {
|
||||
margin: 0;
|
||||
color: var(--color-ink-secondary);
|
||||
padding-bottom: var(--space-4);
|
||||
color: var(--color-ink-tertiary);
|
||||
font-size: var(--type-body-sm);
|
||||
}
|
||||
.ephemeris-ask { min-height: 44px; width: fit-content; }
|
||||
@@ -4915,21 +4960,21 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
.ephemeris-message p { margin: 0; color: var(--color-ink-secondary); font-size: var(--type-body-sm); }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.ephemeris-date-bar { display: grid; grid-template-columns: 1fr 1fr 1fr; }
|
||||
.ephemeris-date-button { width: 100%; }
|
||||
/* The arrows stay arrow-sized and the date keeps the middle: stretching two
|
||||
glyph buttons to a third of the width each is what made the old bar read as
|
||||
three equal actions. */
|
||||
.ephemeris-date-bar { gap: var(--space-1); }
|
||||
.ephemeris-panchanga {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-canvas);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.ephemeris-panchanga-item {
|
||||
grid-template-columns: 5.5rem minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-4);
|
||||
padding: var(--space-3) 0;
|
||||
border: 0;
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-radius: 0;
|
||||
@@ -4941,7 +4986,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
.ephemeris-transits-row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-2) var(--space-4);
|
||||
padding: var(--space-4);
|
||||
padding: var(--space-3) 0;
|
||||
}
|
||||
.ephemeris-transits-row span::before {
|
||||
content: attr(data-label);
|
||||
@@ -4951,6 +4996,7 @@ button.nav-rail-identity:hover { background: var(--sidebar-accent); }
|
||||
font-weight: 500;
|
||||
}
|
||||
.ephemeris-events li { grid-template-columns: 1fr; gap: var(--space-1); }
|
||||
.ephemeris-section { padding: var(--space-4); }
|
||||
.ephemeris-ask { width: 100%; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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