族谱树可视化(2/2):节点-连线树、视野控制与节点交互
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
- 渲染分工:连线与代际轴在一个 SVG 里(坐标即内容层坐标,随内容层缩放);节点是绝对定位的真实 HTML 卡片——要真的可点击/可聚焦/可读屏,不能画成 SVG 矩形 - 交互:拖拽平移(pointer + touch-action:none)、滚轮缩放、放大/缩小按钮(0.35~1.8)、适应窗口、初次进入自动适配一次;hover/键盘聚焦高亮祖先路径 + 整棵子树(可勾选关闭子树),其余节点与连线降透明;当前作品常驻 aria-current 与「当前作品」标记 - 可达性:role=button + tabIndex=0,可访问名形如「第 1 代作品《标题》· 作者」;Enter/Space 等价点击;节点内 Fork 是独立 button 并 stopPropagation,不抢主点击;Fork 复用详情页同一条唤起 + 降级链路(openForkProtocol + FORK_LAUNCH_FALLBACK_DELAY_MS + 复制作品 ID 回退) - 不可用父节点:布局补占位节点(只有 ID 与代际),标题即「原作品已不可用」,副行写「标题与作者名不对外展示」,不渲染作者名与 Fork/对比动作;可聚焦、不可点进详情(role 为空 + aria-disabled) - 边界:截断提示、母版不可用提示、空树空态、加载/失败态照既有组件;超宽扇出与超深链由布局保证不重叠(单测钉住) - 样式:画布 clamp + 点阵背景、连线活跃/降透明、prefers-reduced-motion 关闭过渡、窄屏可横向平移不溢出;删除上一版分组列表的死样式 - 测试:新增 5 条交互/可视化用例,另把两处旧可访问名查询改为 /《母版》/
This commit is contained in:
@@ -723,6 +723,175 @@
|
||||
color: var(--platform-accent-strong);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/*
|
||||
* 族谱树可视化:一个可平移/缩放的画布 + 内容层(CSS transform)。
|
||||
* 连线在 SVG 里(坐标就是内容层坐标,随内容层一起缩放),节点是绝对定位的真实 HTML 卡片
|
||||
* —— 因为节点要真的可点击 / 可聚焦 / 可读屏,不能画成 SVG 矩形。
|
||||
*/
|
||||
.game-lineage-viewport-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.game-lineage-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.game-lineage-fork-notice {
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 0.78rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.game-lineage-fork-copy {
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
border-radius: 999px;
|
||||
padding: 0.2rem 0.7rem;
|
||||
background: transparent;
|
||||
color: var(--platform-text-base);
|
||||
font: inherit;
|
||||
font-size: 0.74rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.game-lineage-canvas {
|
||||
position: relative;
|
||||
height: clamp(22rem, 62vh, 46rem);
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
border-radius: 0.85rem;
|
||||
background: radial-gradient(
|
||||
circle at 1px 1px,
|
||||
var(--platform-surface-border) 1px,
|
||||
transparent 0
|
||||
)
|
||||
0 0 / 22px 22px;
|
||||
cursor: grab;
|
||||
/* 拖拽平移不要触发浏览器自身的滚动/缩放。 */
|
||||
touch-action: none;
|
||||
}
|
||||
.game-lineage-canvas:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.game-lineage-canvas__content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: 0 0;
|
||||
transition: transform 180ms ease-out;
|
||||
will-change: transform;
|
||||
}
|
||||
.game-lineage-canvas__edges {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
}
|
||||
.game-lineage-edge {
|
||||
fill: none;
|
||||
stroke: var(--platform-surface-border);
|
||||
stroke-width: 2;
|
||||
transition:
|
||||
stroke 160ms ease,
|
||||
opacity 160ms ease;
|
||||
}
|
||||
.game-lineage-edge--active {
|
||||
stroke: var(--platform-accent-strong);
|
||||
stroke-width: 2.5;
|
||||
}
|
||||
.game-lineage-edge--dimmed {
|
||||
opacity: 0.18;
|
||||
}
|
||||
.game-lineage-axis {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 0.74rem;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.game-lineage-axis__count {
|
||||
border-radius: 999px;
|
||||
padding: 0.05rem 0.45rem;
|
||||
background: var(--platform-surface-border);
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.game-lineage-node {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
opacity 160ms ease,
|
||||
box-shadow 160ms ease;
|
||||
}
|
||||
.game-lineage-node--dimmed {
|
||||
opacity: 0.34;
|
||||
}
|
||||
.game-lineage-node--stub {
|
||||
border-style: dashed;
|
||||
cursor: default;
|
||||
}
|
||||
.game-lineage-node:focus-visible {
|
||||
outline: 2px solid var(--platform-accent-strong);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.game-lineage-node__title {
|
||||
color: var(--platform-text-strong);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.game-lineage-node__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.game-lineage-node__fork,
|
||||
.game-lineage-node__compare {
|
||||
border: 1px solid var(--platform-surface-border);
|
||||
border-radius: 999px;
|
||||
padding: 0.14rem 0.6rem;
|
||||
background: transparent;
|
||||
color: var(--platform-text-base);
|
||||
font: inherit;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.game-lineage-node__fork:hover,
|
||||
.game-lineage-node__compare:hover {
|
||||
color: var(--platform-text-strong);
|
||||
}
|
||||
.game-lineage-zoom {
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 0.76rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/* 尊重「减少动效」偏好:只保留状态变化,不做过渡动画。 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.game-lineage-canvas__content,
|
||||
.game-lineage-node,
|
||||
.game-lineage-edge {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
/* 窄屏:节点尺寸适合触摸,画布可横向平移(内容层平移而不是页面溢出)。 */
|
||||
@media (max-width: 720px) {
|
||||
.game-lineage-canvas {
|
||||
height: clamp(20rem, 56vh, 34rem);
|
||||
}
|
||||
}
|
||||
.game-loading-panel {
|
||||
display: grid;
|
||||
min-height: 18rem;
|
||||
@@ -1507,40 +1676,6 @@
|
||||
font-size: 0.78rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.game-lineage-tree {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
.game-lineage-generation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.game-lineage-generation__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin: 0;
|
||||
color: var(--platform-text-strong);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.game-lineage-generation__count {
|
||||
border-radius: 999px;
|
||||
padding: 0.05rem 0.5rem;
|
||||
background: var(--platform-surface-border);
|
||||
color: var(--platform-text-soft);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.game-lineage-generation__nodes {
|
||||
display: grid;
|
||||
/* `min(13rem, 100%)`:桌面端列宽不变,视口窄于 13rem 时下限跟着收缩,
|
||||
避免固定下限把内容撑出容器产生横向溢出。 */
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 1fr));
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.game-lineage-node {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user