Swap the cherry-red primary for the logo's blue, bump body 13→15px and h1 32→40px, and add semantic tokens (--bg-*, --accent-*, --text-*) so future palette shifts are a single-file edit. Cherry-red survives only for danger states and the brand cherry mark. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
113 lines
3.6 KiB
Svelte
113 lines
3.6 KiB
Svelte
<script lang="ts">
|
|
interface Props {
|
|
before: string | null;
|
|
after: string | null;
|
|
verb: string;
|
|
relPath: string;
|
|
}
|
|
let { before, after, verb, relPath }: Props = $props();
|
|
|
|
interface Row {
|
|
kind: 'ctx' | 'add' | 'del';
|
|
text: string;
|
|
}
|
|
|
|
let rows = $derived(computeRows(before, after));
|
|
|
|
function computeRows(a: string | null, b: string | null): Row[] {
|
|
const alines = (a ?? '').split(/\r?\n/);
|
|
const blines = (b ?? '').split(/\r?\n/);
|
|
const asetBase: Row[] = alines.map((t) => ({ kind: 'del' as const, text: t }));
|
|
const bsetBase: Row[] = blines.map((t) => ({ kind: 'add' as const, text: t }));
|
|
if (a === null) return bsetBase;
|
|
if (b === null) return asetBase;
|
|
let i = 0;
|
|
while (i < alines.length && i < blines.length && alines[i] === blines[i]) i++;
|
|
let j = 0;
|
|
while (
|
|
j < alines.length - i &&
|
|
j < blines.length - i &&
|
|
alines[alines.length - 1 - j] === blines[blines.length - 1 - j]
|
|
)
|
|
j++;
|
|
const prefix = alines.slice(0, i).map((t) => ({ kind: 'ctx' as const, text: t }));
|
|
const suffix = alines.slice(alines.length - j).map((t) => ({ kind: 'ctx' as const, text: t }));
|
|
const midA = alines.slice(i, alines.length - j).map((t) => ({ kind: 'del' as const, text: t }));
|
|
const midB = blines.slice(i, blines.length - j).map((t) => ({ kind: 'add' as const, text: t }));
|
|
return [...prefix, ...midA, ...midB, ...suffix];
|
|
}
|
|
</script>
|
|
|
|
<div class="diff">
|
|
<header>
|
|
<span class="verb verb-{verb}">{verb}</span>
|
|
<span class="path">{relPath}</span>
|
|
</header>
|
|
<pre>
|
|
{#each rows as r, idx (idx)}<span
|
|
class="ln ln-{r.kind}">{r.kind === 'add' ? '+' : r.kind === 'del' ? '-' : ' '} {r.text}
|
|
</span>{/each}</pre>
|
|
</div>
|
|
|
|
<style>
|
|
.diff {
|
|
background: var(--bg-page);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--r-sm);
|
|
margin: 10px 0;
|
|
overflow: hidden;
|
|
}
|
|
header {
|
|
display: flex;
|
|
gap: 12px;
|
|
padding: 10px 14px;
|
|
background: var(--bg-nav);
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: var(--text-sm);
|
|
align-items: center;
|
|
}
|
|
.verb {
|
|
padding: 3px 10px;
|
|
border-radius: var(--r-sm);
|
|
font-family: var(--font-mono);
|
|
font-size: var(--text-xs);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.8px;
|
|
border: 1px solid transparent;
|
|
}
|
|
.verb-added {
|
|
background: rgba(124, 212, 255, 0.10);
|
|
border-color: rgba(124, 212, 255, 0.3);
|
|
color: var(--spark-500);
|
|
}
|
|
.verb-updated {
|
|
background: rgba(124, 192, 245, 0.10);
|
|
border-color: rgba(124, 192, 245, 0.28);
|
|
color: var(--cream);
|
|
}
|
|
.verb-removed {
|
|
background: rgba(255, 48, 80, 0.1);
|
|
border-color: rgba(255, 48, 80, 0.3);
|
|
color: var(--danger-500);
|
|
}
|
|
.verb-held {
|
|
background: rgba(255, 176, 32, 0.1);
|
|
border-color: rgba(255, 176, 32, 0.3);
|
|
color: var(--amber-500);
|
|
}
|
|
.path { color: var(--ink-100); font-family: var(--font-mono); font-size: var(--text-mono); }
|
|
pre {
|
|
margin: 0;
|
|
padding: 14px 16px;
|
|
font-family: var(--font-mono);
|
|
font-size: var(--text-mono);
|
|
line-height: var(--leading-mono);
|
|
color: var(--ink-300);
|
|
max-height: 540px;
|
|
overflow: auto;
|
|
}
|
|
.ln { display: block; }
|
|
.ln-add { background: rgba(124, 212, 255, 0.08); color: var(--spark-400); }
|
|
.ln-del { background: rgba(255, 48, 80, 0.08); color: #ff8a9a; }
|
|
</style>
|