feat(stammbaum): dim a node when outside the highlighted lineage (#703)

StammbaumNode gains an optional `dimmed` prop that sets group-level
opacity (DIMMED_OPACITY) on the node's root <g>, so the box, accent bar,
name, and dates fade together as one unit. A lineage-fade CSS transition
eases the change and is neutralised under prefers-reduced-motion. The
selected-node styling (active fill + mint accent bar) is untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-31 16:28:22 +02:00
parent 7a655ce6f4
commit f6da95014e

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { NODE_W, NODE_H } from '$lib/person/genealogy/layout/buildLayout';
import { DIMMED_OPACITY } from '$lib/person/genealogy/layout/highlightLineage';
import type { components } from '$lib/generated/api';
type PersonNodeDTO = components['schemas']['PersonNodeDTO'];
@@ -8,10 +9,12 @@ interface Props {
node: PersonNodeDTO;
pos: { x: number; y: number };
selected: boolean;
/** Dim the whole node when a lineage is highlighted and this person is outside it. */
dimmed?: boolean;
onSelect: (id: string) => void;
}
let { node, pos, selected, onSelect }: Props = $props();
let { node, pos, selected, dimmed = false, onSelect }: Props = $props();
// Each node owns its own focus-ring state (the focus ring is decorative; the
// `<g role="button">` is the real focus target).
@@ -35,11 +38,12 @@ const datesLabel = $derived(
aria-label="{node.displayName}{datesLabel}"
aria-expanded={selected}
transform="translate({pos.x}, {pos.y})"
opacity={dimmed ? DIMMED_OPACITY : undefined}
onclick={() => onSelect(node.id)}
onkeydown={handleKey}
onfocus={() => (focused = true)}
onblur={() => (focused = false)}
class="cursor-pointer focus:outline-none"
class="lineage-fade cursor-pointer focus:outline-none"
>
{#if focused}
<rect
@@ -88,3 +92,15 @@ const datesLabel = $derived(
</text>
{/if}
</g>
<style>
/* Ease the lineage focus+dim transition; instant for reduced-motion users. */
.lineage-fade {
transition: opacity 200ms ease;
}
@media (prefers-reduced-motion: reduce) {
.lineage-fade {
transition: none;
}
}
</style>