Merge branch 'refactor/task-10-guide-blocks'
This commit is contained in:
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
// FleetDiagram — orchestrator card, an arrow, and a 1-up of worker cards.
|
||||||
|
//
|
||||||
|
// Static shell: the captain renders literally, the workers render as toggle
|
||||||
|
// buttons with the `data-worker` hook asserted by `scripts/verify.mjs`. The
|
||||||
|
// `initial` worker is marked active and pressed.
|
||||||
|
//
|
||||||
|
// The source uses `gap:1px` over a coloured parent background to fake
|
||||||
|
// hairlines between worker cards; the parent background is an off-token seam
|
||||||
|
// colour marked inline with `token-gap`.
|
||||||
|
|
||||||
|
interface Worker {
|
||||||
|
/** Used as the `data-worker` hook and the key in the source. */
|
||||||
|
id: string;
|
||||||
|
/** Short label rendered uppercase, e.g. "UI". */
|
||||||
|
label: string;
|
||||||
|
/** Body copy describing the worker's remit. */
|
||||||
|
strong: string;
|
||||||
|
/** Path label, e.g. "agent/ui". */
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
orchestrator: {
|
||||||
|
eyebrow: string;
|
||||||
|
/** May contain inline `<br>`; rendered with `set:html`. */
|
||||||
|
title: string;
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
workers: Worker[];
|
||||||
|
initial?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orchestrator, workers, initial = workers[0]?.id } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="fleet-grid">
|
||||||
|
<article class="captain">
|
||||||
|
<span class="captain-eyebrow">{orchestrator.eyebrow}</span>
|
||||||
|
<h2 set:html={orchestrator.title} />
|
||||||
|
<code>{orchestrator.code}</code>
|
||||||
|
</article>
|
||||||
|
<div class="arrow" aria-hidden="true">→</div>
|
||||||
|
<div class="workers" role="group" aria-label="Worker agents">
|
||||||
|
{
|
||||||
|
workers.map((worker) => (
|
||||||
|
<button
|
||||||
|
class:list={['worker-card', { active: worker.id === initial }]}
|
||||||
|
data-worker={worker.id}
|
||||||
|
aria-pressed={worker.id === initial}
|
||||||
|
>
|
||||||
|
<span>{worker.label}</span>
|
||||||
|
<strong>{worker.strong}</strong>
|
||||||
|
<code>{worker.code}</code>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fleet-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.8fr 50px 1.5fr;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 30px;
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--deep);
|
||||||
|
}
|
||||||
|
.captain {
|
||||||
|
display: grid;
|
||||||
|
align-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
.captain-eyebrow {
|
||||||
|
color: var(--gold);
|
||||||
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 10px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.captain h2 {
|
||||||
|
margin: 0;
|
||||||
|
/* token-gap: source uses clamp(24px,3vw,38px); --step-5 is clamp(36px,5vw,65px); owner design-system-keeper */
|
||||||
|
font-size: clamp(24px, 3vw, 38px);
|
||||||
|
line-height: 0.98;
|
||||||
|
letter-spacing: -0.06em;
|
||||||
|
}
|
||||||
|
.captain code {
|
||||||
|
color: var(--gold);
|
||||||
|
font:
|
||||||
|
500 var(--step-0) 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
color: var(--gold);
|
||||||
|
/* token-gap: source uses 30px; no --step-* covers 30px; owner design-system-keeper */
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workers {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 1px;
|
||||||
|
/* token-gap: source seam colour is #41596b for the gap:1px hairline trick; no token matches; owner design-system-keeper */
|
||||||
|
background: #41596b;
|
||||||
|
}
|
||||||
|
.worker-card {
|
||||||
|
display: grid;
|
||||||
|
align-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
min-height: 180px;
|
||||||
|
padding: 22px;
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--ink);
|
||||||
|
border: 0;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.worker-card span {
|
||||||
|
/* token-gap: source uses #9eabb4; no token matches; owner design-system-keeper */
|
||||||
|
color: #9eabb4;
|
||||||
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 10px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.worker-card strong {
|
||||||
|
/* token-gap: source uses 16px; no --step-* covers 16px; owner design-system-keeper */
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.worker-card code {
|
||||||
|
color: var(--gold);
|
||||||
|
font:
|
||||||
|
500 var(--step-0) 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
.worker-card.active {
|
||||||
|
box-shadow: inset 4px 0 0 var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.fleet-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
min-height: 35px;
|
||||||
|
}
|
||||||
|
.workers {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
// HandoffTable — the four-row "what crosses contexts" table.
|
||||||
|
//
|
||||||
|
// Static shell: the row data is passed in via `rows` so this component has
|
||||||
|
// no opinion on what each handoff package contains. The table structure is
|
||||||
|
// the load-bearing part of the design (dark header, blue row labels,
|
||||||
|
// muted body) and lives here so the next page that needs it gets the same
|
||||||
|
// beat for free.
|
||||||
|
|
||||||
|
interface Row {
|
||||||
|
/** The package name, rendered as a `<th>` (column 1). */
|
||||||
|
package: string;
|
||||||
|
/** What the package contains (column 2). */
|
||||||
|
contains: string;
|
||||||
|
/** Why this matters (column 3). */
|
||||||
|
why: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** Column headers in render order. */
|
||||||
|
columns: [string, string, string];
|
||||||
|
rows: Row[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { columns, rows } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<table class="handoff-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{columns[0]}</th>
|
||||||
|
<th>{columns[1]}</th>
|
||||||
|
<th>{columns[2]}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
rows.map((row) => (
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{row.package}</th>
|
||||||
|
<td>{row.contains}</td>
|
||||||
|
<td>{row.why}</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.handoff-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 30px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.handoff-table th,
|
||||||
|
.handoff-table td {
|
||||||
|
padding: 17px 14px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
.handoff-table thead {
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--ink);
|
||||||
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 10px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.handoff-table tbody th {
|
||||||
|
color: var(--blue);
|
||||||
|
/* token-gap: source uses 14px; no --step-* covers 14px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
600 14px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
.handoff-table td {
|
||||||
|
color: var(--muted);
|
||||||
|
/* token-gap: source uses 13px; no --step-* covers 13px; owner design-system-keeper */
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.handoff-table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.handoff-table {
|
||||||
|
min-width: 650px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
---
|
||||||
|
// PhasePanel — the three-step "Click a phase / see the handoff" block.
|
||||||
|
//
|
||||||
|
// Static shell: the tab buttons and the panel markup are server-rendered.
|
||||||
|
// The `initial` phase's content is shown by default; the other tabs still
|
||||||
|
// carry the `data-phase` hook so the interactive island (task 15) can swap
|
||||||
|
// the panel on click.
|
||||||
|
//
|
||||||
|
// Every `data-phase` value is asserted by `scripts/verify.mjs`.
|
||||||
|
|
||||||
|
export type PhaseId = 'plan' | 'build' | 'review';
|
||||||
|
|
||||||
|
interface Phase {
|
||||||
|
id: PhaseId;
|
||||||
|
/** Two-letter label rendered in the tab, e.g. "PLAN". */
|
||||||
|
label: string;
|
||||||
|
/** Numeric prefix, e.g. "01". */
|
||||||
|
number: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
/** Headline + small caption shown above the panel title. */
|
||||||
|
meta: { deliverable: string; gate: string };
|
||||||
|
/** Code-line evidence shown at the bottom of the panel. */
|
||||||
|
evidence: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
phases: Phase[];
|
||||||
|
/** Initial active phase. Defaults to the first entry. */
|
||||||
|
initial?: PhaseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { phases, initial = phases[0]?.id ?? 'plan' } = Astro.props;
|
||||||
|
const active = phases.find((phase) => phase.id === initial) ?? phases[0];
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="phase-tabs" role="tablist" aria-label="Workflow phases">
|
||||||
|
{
|
||||||
|
phases.map((phase) => (
|
||||||
|
<button
|
||||||
|
class:list={['phase-tab', { active: phase.id === initial }]}
|
||||||
|
data-phase={phase.id}
|
||||||
|
role="tab"
|
||||||
|
aria-selected={phase.id === initial}
|
||||||
|
>
|
||||||
|
<b>{phase.number}</b> {phase.label}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="phase-panel" id="phase-panel" aria-live="polite">
|
||||||
|
<div class="phase-meta">
|
||||||
|
<span>{active.meta.deliverable}</span>
|
||||||
|
<small>{active.meta.gate}</small>
|
||||||
|
</div>
|
||||||
|
<h3>{active.title}</h3>
|
||||||
|
<p>{active.body}</p>
|
||||||
|
<code>{active.evidence}</code>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.phase-tabs {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-tab {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 42px 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
color: var(--ink);
|
||||||
|
background: transparent;
|
||||||
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 10px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.phase-tab b {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.phase-tab.active {
|
||||||
|
color: var(--paper);
|
||||||
|
border-color: var(--ink);
|
||||||
|
background: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-panel {
|
||||||
|
min-height: 260px;
|
||||||
|
padding: 30px;
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
.phase-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 15px;
|
||||||
|
color: var(--gold);
|
||||||
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 10px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
.phase-meta small {
|
||||||
|
color: var(--paper);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.phase-panel h3 {
|
||||||
|
margin: 44px 0 12px;
|
||||||
|
/* token-gap: source uses clamp(24px,3vw,38px); --step-5 is clamp(36px,5vw,65px); owner design-system-keeper */
|
||||||
|
font-size: clamp(24px, 3vw, 38px);
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
.phase-panel p {
|
||||||
|
line-height: 1.6;
|
||||||
|
opacity: 0.82;
|
||||||
|
}
|
||||||
|
.phase-panel code {
|
||||||
|
display: block;
|
||||||
|
margin-top: 26px;
|
||||||
|
color: var(--gold);
|
||||||
|
font:
|
||||||
|
500 var(--step-0) 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
// RouteTable — the model-routing matrix. Four buttons (one per job profile),
|
||||||
|
// each carrying the `data-route` hook asserted by `scripts/verify.mjs`.
|
||||||
|
//
|
||||||
|
// Static shell: the initial route is marked active and pressed. The shell
|
||||||
|
// renders the column header + the four rows; task 15 will hydrate the
|
||||||
|
// "route detail" panel to the right.
|
||||||
|
|
||||||
|
interface Route {
|
||||||
|
id: 'plan' | 'build' | 'explore' | 'review' | string;
|
||||||
|
/** Strong label, e.g. "Plan". */
|
||||||
|
strong: string;
|
||||||
|
/** Profile descriptor, e.g. "strong / broad". */
|
||||||
|
profile: string;
|
||||||
|
/** Prompt shape copy. */
|
||||||
|
prompt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
routes: Route[];
|
||||||
|
initial?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { routes, initial = routes[0]?.id ?? 'plan' } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="route-table">
|
||||||
|
<div class="head">
|
||||||
|
<span>Work</span>
|
||||||
|
<span>Profile</span>
|
||||||
|
<span>Prompt shape</span>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
routes.map((route) => (
|
||||||
|
<button
|
||||||
|
class:list={['route-row', { active: route.id === initial }]}
|
||||||
|
data-route={route.id}
|
||||||
|
aria-pressed={route.id === initial}
|
||||||
|
>
|
||||||
|
<strong>{route.strong}</strong>
|
||||||
|
<b>{route.profile}</b>
|
||||||
|
<small>{route.prompt}</small>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.route-table {
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
border-left: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.head,
|
||||||
|
.route-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.8fr 0.9fr 1.4fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head {
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--ink);
|
||||||
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 10px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.head > *,
|
||||||
|
.route-row > * {
|
||||||
|
padding: 15px;
|
||||||
|
border-right: 1px solid var(--line);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-row {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.route-row strong {
|
||||||
|
/* token-gap: source uses 14px; no --step-* covers 14px; owner design-system-keeper */
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.route-row b {
|
||||||
|
color: var(--blue);
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
.route-row small {
|
||||||
|
color: var(--muted);
|
||||||
|
/* token-gap: source uses 12px; no --step-* covers 12px; owner design-system-keeper */
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.route-row.active {
|
||||||
|
background: var(--line);
|
||||||
|
}
|
||||||
|
.route-row.active strong {
|
||||||
|
box-shadow: inset 4px 0 0 var(--gold);
|
||||||
|
}
|
||||||
|
.route-row:focus-visible {
|
||||||
|
outline: 3px solid var(--gold);
|
||||||
|
outline-offset: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.route-table {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.head,
|
||||||
|
.route-row {
|
||||||
|
min-width: 620px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
// SkillPackage — the four-file skill-package picker (SKILL.md, references/,
|
||||||
|
// scripts/, assets/). Buttons carry the `data-skill-file` hook asserted by
|
||||||
|
// `scripts/verify.mjs`.
|
||||||
|
//
|
||||||
|
// Static shell: the initial file is marked active and selected. The block is
|
||||||
|
// paired with a `<article>` detail panel by the parent page; this component
|
||||||
|
// renders only the picker.
|
||||||
|
|
||||||
|
interface PackageFile {
|
||||||
|
id: 'skill' | 'references' | 'scripts' | 'assets' | string;
|
||||||
|
/** Path rendered inside `<code>`, e.g. "SKILL.md". */
|
||||||
|
path: string;
|
||||||
|
/** Helper copy under the path. */
|
||||||
|
small: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
files: PackageFile[];
|
||||||
|
initial?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { files, initial = files[0]?.id ?? 'skill' } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="skill-package" role="tree" aria-label="Skill package files">
|
||||||
|
<span class="skill-package-label">SKILL PACKAGE</span>
|
||||||
|
{
|
||||||
|
files.map((file) => (
|
||||||
|
<button
|
||||||
|
class:list={['skill-package-row', { active: file.id === initial }]}
|
||||||
|
data-skill-file={file.id}
|
||||||
|
role="treeitem"
|
||||||
|
aria-selected={file.id === initial}
|
||||||
|
>
|
||||||
|
<code>{file.path}</code>
|
||||||
|
<small>{file.small}</small>
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.skill-package {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
color: var(--paper);
|
||||||
|
background: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-package-label {
|
||||||
|
padding: 20px;
|
||||||
|
color: var(--gold);
|
||||||
|
/* token-gap: source uses 1px solid #ffffff40 over the blue background; no token matches; owner design-system-keeper */
|
||||||
|
border-bottom: 1px solid #ffffff40;
|
||||||
|
/* token-gap: source uses 10px; no --step-* covers 10px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 10px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-package-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 17px 20px;
|
||||||
|
border: 0;
|
||||||
|
/* token-gap: source uses 1px solid #ffffff40 over the blue background; no token matches; owner design-system-keeper */
|
||||||
|
border-bottom: 1px solid #ffffff40;
|
||||||
|
color: var(--paper);
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.skill-package-row code {
|
||||||
|
/* token-gap: source uses 12px with default weight (no 500); no --step-* covers 12px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
12px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
.skill-package-row small {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.skill-package-row.active {
|
||||||
|
/* House style: left bar via inset box-shadow, not a border. */
|
||||||
|
box-shadow: inset 4px 0 0 var(--gold);
|
||||||
|
}
|
||||||
|
.skill-package-row:focus-visible {
|
||||||
|
outline: 3px solid var(--gold);
|
||||||
|
outline-offset: -3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
---
|
||||||
|
// 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);
|
||||||
|
/* token-gap: source uses 1px solid #41596b over the ink background; no token matches; owner design-system-keeper */
|
||||||
|
border: 1px solid #41596b;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tree-node.root {
|
||||||
|
background: var(--gold);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.tree-node span {
|
||||||
|
color: var(--accent);
|
||||||
|
/* token-gap: source uses 9px; no --step-* covers 9px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
500 9px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.tree-node strong {
|
||||||
|
/* token-gap: source uses 14px with default weight; no --step-* covers 14px; owner design-system-keeper */
|
||||||
|
font:
|
||||||
|
14px 'DM Mono',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
.tree-node small {
|
||||||
|
margin-top: 0;
|
||||||
|
/* token-gap: source uses #9eabb4; no token matches; owner design-system-keeper */
|
||||||
|
color: #9eabb4;
|
||||||
|
font-size: var(--step-0);
|
||||||
|
}
|
||||||
|
.tree-node.root small {
|
||||||
|
/* token-gap: source uses #9eabb4 even on the root; no token matches; owner design-system-keeper */
|
||||||
|
color: #9eabb4;
|
||||||
|
}
|
||||||
|
.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