Files
ai-for-dummies/src/components/blocks/ChangeLens.astro
T
Marcos Paulo 9ca3f48def style(motion): add strict animations and fix layout properties
- 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
2026-09-05 23:13:59 +00:00

386 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
// 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>