feat(blocks): extract six full-guide block components
PhasePanel, FleetDiagram, HandoffTable, WorktreeMap, RouteTable, SkillPackage as static shells. Each takes typed props and renders server-side markup with the data-* hooks verified by scripts/verify.mjs (data-phase, data-tree, data-worker, data-route, data-skill-file). No client:* directives; interactive islands wire up in task 15. Tokens only. No raw hex or px font sizes (check-tokens passes). The parent-background seam colour for the gap:1px grid trick in FleetDiagram is a documented token gap; see component header comment and the task final report.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
---
|
||||
// WorktreeMap — the repository-topology diagram with the SVG trunk-and-branches
|
||||
// behind four `tree-node` buttons.
|
||||
//
|
||||
// Static shell: the SVG paths render server-side; the nodes are buttons with
|
||||
// the `data-tree` hook asserted by `scripts/verify.mjs`. The `root` node and
|
||||
// the `initial` branch are marked selected.
|
||||
|
||||
interface Branch {
|
||||
/** The `data-tree` hook, e.g. "ui", "tests", "docs". */
|
||||
id: string;
|
||||
/** Uppercase label, e.g. "UI AGENT". */
|
||||
label: string;
|
||||
/** Strong line, e.g. "agent/ui". */
|
||||
strong: string;
|
||||
/** Status small, e.g. "3 files · working". */
|
||||
small: string;
|
||||
/** CSS modifier so each branch picks up its tone. */
|
||||
tone: 'ui' | 'tests' | 'docs' | string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
branches: Branch[];
|
||||
initial?: string;
|
||||
}
|
||||
|
||||
const { branches, initial = 'main' } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="tree-stage" role="tree" aria-label="Repository worktree topology">
|
||||
<svg viewBox="0 0 760 330" preserveAspectRatio="none" aria-hidden="true">
|
||||
<path class="tree-edge trunk" d="M380 48 V118"></path>
|
||||
<path class="tree-edge" d="M380 118 C380 170 110 150 110 224"></path>
|
||||
<path class="tree-edge" d="M380 118 V224"></path>
|
||||
<path class="tree-edge" d="M380 118 C380 170 650 150 650 224"></path>
|
||||
</svg>
|
||||
<button
|
||||
class:list={['tree-node', 'root', { active: initial === 'main' }]}
|
||||
data-tree="main"
|
||||
role="treeitem"
|
||||
aria-selected={initial === 'main'}
|
||||
><span>ROOT</span><strong>main</strong><small>● clean</small></button
|
||||
>
|
||||
{
|
||||
branches.map((branch) => (
|
||||
<button
|
||||
class:list={['tree-node', 'branch', branch.tone, { active: branch.id === initial }]}
|
||||
data-tree={branch.id}
|
||||
role="treeitem"
|
||||
aria-selected={branch.id === initial}
|
||||
>
|
||||
<>
|
||||
<span>{branch.label}</span>
|
||||
<strong>{branch.strong}</strong>
|
||||
<small>{branch.small}</small>
|
||||
</>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tree-stage {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px;
|
||||
padding-top: 90px;
|
||||
}
|
||||
|
||||
/* SVG trunk-and-branches behind the buttons. */
|
||||
.tree-stage svg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.tree-edge {
|
||||
fill: none;
|
||||
stroke: var(--gold);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
/* Root sits at the top centre, spanning both columns. */
|
||||
.tree-node.root {
|
||||
grid-column: 1 / -1;
|
||||
justify-self: center;
|
||||
width: 180px;
|
||||
}
|
||||
.tree-node {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 12px;
|
||||
min-height: 135px;
|
||||
padding: 20px;
|
||||
color: var(--paper);
|
||||
background: var(--ink);
|
||||
border: 0;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tree-node.root {
|
||||
background: var(--gold);
|
||||
color: var(--ink);
|
||||
}
|
||||
.tree-node span {
|
||||
color: var(--accent);
|
||||
font:
|
||||
500 var(--step-0) 'DM Mono',
|
||||
monospace;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.tree-node strong {
|
||||
font:
|
||||
600 var(--step-1) 'DM Mono',
|
||||
monospace;
|
||||
}
|
||||
.tree-node small {
|
||||
margin-top: 0;
|
||||
color: var(--muted);
|
||||
font-size: var(--step-0);
|
||||
}
|
||||
.tree-node.root small {
|
||||
color: var(--muted);
|
||||
}
|
||||
.tree-node.active {
|
||||
box-shadow: inset 4px 0 0 var(--gold);
|
||||
}
|
||||
.tree-node:focus-visible {
|
||||
outline: 3px solid var(--gold);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
/* Branch tone variants — left/right placement stays on the grid columns. */
|
||||
.tree-node.branch.ui {
|
||||
grid-column: 1;
|
||||
}
|
||||
.tree-node.branch.tests {
|
||||
grid-column: 1 / -1;
|
||||
justify-self: center;
|
||||
}
|
||||
.tree-node.branch.docs {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.tree-stage {
|
||||
grid-template-columns: 1fr;
|
||||
padding-top: 90px;
|
||||
}
|
||||
.tree-node.branch.ui,
|
||||
.tree-node.branch.tests,
|
||||
.tree-node.branch.docs {
|
||||
grid-column: 1;
|
||||
justify-self: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user