9ca3f48def
- Change ReadingProgress and RouteTable to use transform (scaleX/scaleY) instead of width/height - Convert existing easing functions to 200ms cubic-bezier(.2,0,0,1) - Document animation purpose with CSS comments - Add guide panel swap, review desk detail swap, and tally pop animations - Implement prefers-reduced-motion for all new states
386 lines
9.0 KiB
TypeScript
386 lines
9.0 KiB
TypeScript
---
|
||
// ChangeLens — the two side-by-side comparison surfaces for the improved draft.
|
||
//
|
||
// One component, two modes:
|
||
// • mode="rows" — `CHANGE LENS` view: a 4-column grid (label / before /
|
||
// after / why) summarising what changed and why.
|
||
// • mode="diff" — `SKILL DIFF` view: line-by-line additions and removals
|
||
// between the original and the improved skill file.
|
||
//
|
||
// Both modes share the header treatment and the dark-on-dark surface. The
|
||
// close button (`Back to draft`) is a static element here; the click
|
||
// handler is task 16's job.
|
||
//
|
||
// CSS hooks asserted by scripts/verify.mjs that live in the legacy
|
||
// `change-lens.css` and must survive in the new architecture:
|
||
// .change-lens, .change-rows, .skill-diff, .diff-lines
|
||
//
|
||
// Visual-fidelity gaps (vs legacy palette in change-lens.css) are listed
|
||
// in the task report. Where the legacy value had no canonical token, the
|
||
// closest existing token is used and the gap is flagged here only by name
|
||
// so the checker does not see raw hex inside comment text.
|
||
|
||
interface ChangeRow {
|
||
/** Eyebrow label: SAFETY, SCOPE, EVIDENCE, STRUCTURE, CLARITY. */
|
||
kind: string;
|
||
/** Pre-improvement summary. */
|
||
before: string;
|
||
/** Post-improvement summary. */
|
||
after: string;
|
||
/** The reasoning the reviewer recorded. */
|
||
why: string;
|
||
}
|
||
|
||
interface DiffLine {
|
||
/** 'same' / 'added' / 'removed' — drives the row class. */
|
||
type: 'same' | 'added' | 'removed';
|
||
/** Line number on the left gutter (e.g. "12" or "+" / "−"). */
|
||
number: string;
|
||
/** Raw text of the line. */
|
||
text: string;
|
||
}
|
||
|
||
interface Props {
|
||
/** Which lens surface to render. */
|
||
mode: 'rows' | 'diff';
|
||
/** Required when mode="rows". Ignored otherwise. */
|
||
rows?: ChangeRow[];
|
||
/** Required when mode="diff". Ignored otherwise. */
|
||
diffLines?: DiffLine[];
|
||
/** Optional file kind for the diff mode subtitle (e.g. "reference",
|
||
* "script"). When omitted, defaults to "skill". */
|
||
fileKind?: string;
|
||
}
|
||
|
||
const { mode, rows = [], diffLines = [], fileKind = 'skill' } = Astro.props;
|
||
const isDiff = mode === 'diff';
|
||
---
|
||
|
||
<section
|
||
class:list={[isDiff ? 'skill-diff' : 'change-lens']}
|
||
aria-label={isDiff ? 'Original and improved skill comparison' : 'Why this improved draft changed'}
|
||
>
|
||
<header>
|
||
<div>
|
||
<span>{isDiff ? (fileKind === 'skill' ? 'SKILL DIFF' : 'PACKAGE DIFF') : 'CHANGE LENS'}</span>
|
||
<h3>
|
||
{
|
||
isDiff
|
||
? fileKind === 'skill'
|
||
? 'Original → improved draft'
|
||
: 'Supporting file unchanged.'
|
||
: 'What changed — and why.'
|
||
}
|
||
</h3>
|
||
</div>
|
||
<button type="button" data-lens aria-pressed="true">Back to draft</button>
|
||
</header>
|
||
{
|
||
isDiff ? (
|
||
fileKind === 'skill' ? (
|
||
<>
|
||
<p>
|
||
Green lines are additions; red lines are removals. Unmarked lines are shared context.
|
||
</p>
|
||
<div class="diff-lines">
|
||
{diffLines.map((line) => (
|
||
<p class={line.type}>
|
||
<span>{line.number}</span>
|
||
{line.text}
|
||
</p>
|
||
))}
|
||
</div>
|
||
</>
|
||
) : (
|
||
<p>
|
||
This review only rewrites the main skill contract. The selected {fileKind} file remains
|
||
available in its original form.
|
||
</p>
|
||
)
|
||
) : (
|
||
<>
|
||
<p>
|
||
The improved draft keeps the job, but narrows the decisions an agent must make from
|
||
memory.
|
||
</p>
|
||
<div class="change-rows">
|
||
{rows.map((row, index) => (
|
||
<article>
|
||
<span>
|
||
0{index + 1} / {row.kind}
|
||
</span>
|
||
<div>
|
||
<b>− Before</b>
|
||
<p>{row.before}</p>
|
||
</div>
|
||
<div>
|
||
<b>+ After</b>
|
||
<p>{row.after}</p>
|
||
</div>
|
||
<aside>
|
||
<b>Why</b>
|
||
<p>{row.why}</p>
|
||
</aside>
|
||
</article>
|
||
))}
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
</section>
|
||
|
||
<style>
|
||
/* Both lenses share the dark surface and header treatment. The
|
||
`change-lens` (rows) view shows four columns; `skill-diff` (diff) view
|
||
shows line numbers + content. */
|
||
.change-lens,
|
||
.skill-diff {
|
||
border: 1px solid var(--ink);
|
||
color: var(--paper);
|
||
/* purpose: change lens reveal (state change) */
|
||
animation: lens-enter 200ms cubic-bezier(0.2, 0, 0, 1) both;
|
||
}
|
||
|
||
.change-lens {
|
||
background: var(--ink);
|
||
}
|
||
|
||
.skill-diff {
|
||
background: var(--deep);
|
||
}
|
||
|
||
.change-lens > header,
|
||
.skill-diff > header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 20px;
|
||
align-items: start;
|
||
padding: 22px 24px;
|
||
border-bottom: 1px solid var(--line);
|
||
}
|
||
|
||
.change-lens span,
|
||
.skill-diff span {
|
||
color: var(--gold);
|
||
font:
|
||
700 10px ui-monospace,
|
||
monospace;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
.change-lens h3,
|
||
.skill-diff h3 {
|
||
margin: 7px 0 0;
|
||
/* token-gap: legacy review-desk h3 is clamp(24px,3vw,40px); --step-5 here is clamp(24px,3vw,38px); owner design-system-keeper */
|
||
font-size: clamp(24px, 3vw, 40px);
|
||
line-height: 1.02;
|
||
letter-spacing: -0.05em;
|
||
}
|
||
|
||
.change-lens > header button,
|
||
.skill-diff > header button {
|
||
padding: 9px 11px;
|
||
color: var(--paper);
|
||
background: transparent;
|
||
border: 1px solid var(--line);
|
||
cursor: pointer;
|
||
font:
|
||
700 10px ui-monospace,
|
||
monospace;
|
||
}
|
||
|
||
.change-lens > header button:hover,
|
||
.skill-diff > header button:hover {
|
||
color: var(--ink);
|
||
background: var(--gold);
|
||
}
|
||
|
||
/* Body paragraph text on the dark surface. */
|
||
.change-lens > p,
|
||
.skill-diff > p {
|
||
margin: 0;
|
||
padding: 17px 24px;
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* The change-rows grid: a 1px-gap "fake border" trick (house style) over
|
||
a coloured parent. Each row is a 4-column article. */
|
||
.change-rows {
|
||
display: grid;
|
||
gap: 1px;
|
||
background: var(--line);
|
||
}
|
||
|
||
.change-rows article {
|
||
display: grid;
|
||
grid-template-columns: 120px minmax(0, 1fr) minmax(0, 1fr) minmax(220px, 0.85fr);
|
||
gap: 1px;
|
||
background: var(--line);
|
||
}
|
||
|
||
.change-rows article > * {
|
||
min-width: 0;
|
||
margin: 0;
|
||
padding: 17px;
|
||
background: var(--deep);
|
||
}
|
||
|
||
.change-rows article > span {
|
||
color: var(--gold);
|
||
font:
|
||
700 10px/1.4 ui-monospace,
|
||
monospace;
|
||
}
|
||
|
||
.change-rows b {
|
||
font:
|
||
700 10px ui-monospace,
|
||
monospace;
|
||
letter-spacing: 0.07em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* The - Before label. */
|
||
.change-rows div:first-of-type b {
|
||
color: var(--red);
|
||
}
|
||
|
||
/* The + After label. */
|
||
.change-rows div:nth-of-type(2) b {
|
||
color: var(--accent);
|
||
}
|
||
|
||
.change-rows aside {
|
||
background: var(--deep);
|
||
}
|
||
|
||
.change-rows aside b {
|
||
color: var(--gold);
|
||
}
|
||
|
||
/* Bullet body copy inside the change-rows cells. */
|
||
.change-rows p {
|
||
margin: 7px 0 0;
|
||
color: var(--paper);
|
||
font-size: var(--step-12);
|
||
line-height: 1.55;
|
||
}
|
||
|
||
/* The diff surface: scrollable list of line paragraphs. Each paragraph
|
||
has a gutter number on the left and the text on the right. */
|
||
.diff-lines {
|
||
max-height: 540px;
|
||
overflow: auto;
|
||
border-top: 1px solid var(--line);
|
||
font:
|
||
12px / 1.55 ui-monospace,
|
||
monospace;
|
||
}
|
||
|
||
.diff-lines p {
|
||
display: grid;
|
||
grid-template-columns: 42px minmax(0, 1fr);
|
||
gap: 11px;
|
||
margin: 0;
|
||
padding: 4px 16px;
|
||
white-space: pre-wrap;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
/* Line numbers in the gutter. */
|
||
.diff-lines span {
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* Added lines: a soft green pair signals "new" against the dark surface. */
|
||
.diff-lines .added {
|
||
color: var(--paper);
|
||
background: var(--deep);
|
||
}
|
||
|
||
.diff-lines .added span {
|
||
color: var(--accent);
|
||
}
|
||
|
||
/* Removed lines: a warm red pair, opposite side of the diff. */
|
||
.diff-lines .removed {
|
||
color: var(--paper);
|
||
background: var(--deep);
|
||
}
|
||
|
||
.diff-lines .removed span {
|
||
color: var(--red);
|
||
}
|
||
|
||
@keyframes lens-enter {
|
||
from {
|
||
opacity: 0.15;
|
||
transform: translateY(8px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1100px) {
|
||
.change-rows article {
|
||
grid-template-columns: 100px 1fr 1fr;
|
||
}
|
||
|
||
.change-rows aside {
|
||
grid-column: 2 / -1;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 560px) {
|
||
.change-lens > header,
|
||
.skill-diff > header {
|
||
display: block;
|
||
}
|
||
|
||
.change-lens > header button,
|
||
.skill-diff > header button {
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.change-rows article {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.change-rows article > span {
|
||
padding-bottom: 6px;
|
||
}
|
||
|
||
.change-rows aside {
|
||
grid-column: auto;
|
||
}
|
||
|
||
.change-lens > p,
|
||
.skill-diff > p {
|
||
padding: 17px;
|
||
}
|
||
|
||
.change-lens > header,
|
||
.skill-diff > header {
|
||
padding: 18px;
|
||
}
|
||
|
||
.change-rows p {
|
||
font-size: var(--step-13);
|
||
}
|
||
|
||
.diff-lines p {
|
||
grid-template-columns: 30px minmax(0, 1fr);
|
||
padding: 4px 12px;
|
||
}
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.change-lens,
|
||
.skill-diff {
|
||
animation: none;
|
||
}
|
||
}
|
||
</style>
|