Skip to content

Commit bbba7b7

Browse files
committed
updated proof visualization, changed style/theme
1 parent 80424c2 commit bbba7b7

7 files changed

Lines changed: 335 additions & 98 deletions

File tree

docusaurus-site/docusaurus.config.js

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,18 +116,8 @@ const config = {
116116
],
117117
},
118118
footer: {
119-
style: 'dark',
120-
links: [
121-
{
122-
title: 'Docs',
123-
items: [
124-
{
125-
label: 'Tutorial',
126-
to: '/docs/tutorial/intro',
127-
},
128-
],
129-
},
130-
],
119+
style: 'light',
120+
links: [],
131121
copyright: `Copyright © ${new Date().getFullYear()}. Built with Docusaurus.`,
132122
},
133123

docusaurus-site/src/components/ProofSearchCanvas.js

Lines changed: 169 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,53 @@
22
import React, {useEffect, useRef} from 'react';
33

44
const LIGHT_COLORS = {
5-
new: '#46bdf0',
6-
active: '#42d392',
7-
passive: '#9aa6b2',
8-
selected: '#ffb347',
9-
default: '#6b93ff',
10-
text: '#0c1622',
11-
glow: 'rgba(74, 157, 255, 0.25)',
12-
bg0: '#f7fbff',
13-
bg1: '#edf3ff',
14-
grid: 'rgba(100, 125, 160, 0.12)',
15-
link: 'rgba(70, 110, 200, 0.35)',
16-
tooltipBg: 'rgba(15, 24, 44, 0.9)',
17-
tooltipStroke: 'rgba(255,255,255,0.15)',
18-
tooltipText: '#f5f7ff',
19-
subsumed: '#ff6b6b',
20-
highlight: '#ffe75a',
5+
new: '#ff9fb2',
6+
active: '#b62929',
7+
passive: '#8a8a8a',
8+
selected: '#f97316',
9+
default: '#1a1a1a',
10+
text: '#0b0b0b',
11+
glow: 'rgba(140, 20, 20, 0.22)',
12+
bg0: '#fbfbfb',
13+
bg1: '#f2f2f2',
14+
grid: 'rgba(20, 20, 20, 0.08)',
15+
link: 'rgba(60, 60, 60, 0.35)',
16+
tooltipBg: 'rgba(18, 12, 12, 0.9)',
17+
tooltipStroke: 'rgba(255,255,255,0.12)',
18+
tooltipText: '#f8f4f4',
19+
tooltipOp: '#ff6b6b',
20+
tooltipVar: '#ffd166',
21+
tooltipNum: '#f59e0b',
22+
tooltipBool: '#ffffff',
23+
tooltipSym: '#f8f4f4',
24+
tooltipPunct: '#c0b0b0',
25+
subsumed: '#ff4d4d',
26+
highlight: '#fff59d',
2127
};
2228

2329
const DARK_COLORS = {
24-
new: '#5bc5ff',
25-
active: '#41e6a4',
26-
passive: '#7e8a96',
27-
selected: '#ffb347',
28-
default: '#7aa2f7',
29-
text: '#eef2ff',
30-
glow: 'rgba(94, 197, 255, 0.25)',
31-
bg0: '#0b1020',
32-
bg1: '#121a2f',
33-
grid: 'rgba(120, 150, 200, 0.12)',
34-
link: 'rgba(110, 170, 255, 0.35)',
35-
tooltipBg: 'rgba(20, 27, 44, 0.92)',
30+
new: '#ff3b5c',
31+
active: '#9a1b1b',
32+
passive: '#5f5f5f',
33+
selected: '#f59e0b',
34+
default: '#e0e0e0',
35+
text: '#f4f1f1',
36+
glow: 'rgba(255, 40, 40, 0.22)',
37+
bg0: '#0a0a0a',
38+
bg1: '#101010',
39+
grid: 'rgba(255, 255, 255, 0.08)',
40+
link: 'rgba(200, 200, 200, 0.28)',
41+
tooltipBg: 'rgba(18, 10, 10, 0.95)',
3642
tooltipStroke: 'rgba(255,255,255,0.2)',
37-
tooltipText: '#e6edf7',
38-
subsumed: '#ff6b6b',
39-
highlight: '#ffe75a',
43+
tooltipText: '#f3eaea',
44+
tooltipOp: '#ff8080',
45+
tooltipVar: '#ffd78a',
46+
tooltipNum: '#fbbf24',
47+
tooltipBool: '#ffffff',
48+
tooltipSym: '#f3eaea',
49+
tooltipPunct: '#b9aaaa',
50+
subsumed: '#ff4d4d',
51+
highlight: '#fff59d',
4052
};
4153

4254
function getPalette(theme) {
@@ -72,6 +84,7 @@ export default function ProofSearchCanvas({
7284
const timeRef = useRef(0);
7385
const themeRef = useRef('light');
7486
const tapRef = useRef({time: 0, id: null, x: 0, y: 0});
87+
const hoverFadeRef = useRef({id: null, alpha: 0, lastTs: 0});
7588

7689
useEffect(() => {
7790
nodesRef.current.clear();
@@ -318,8 +331,28 @@ export default function ProofSearchCanvas({
318331

319332
// Directed edges from parents to children
320333
const edgeList = edgesRef.current;
321-
const hoverId = hoverRef.current?.node?.id;
322-
const highlight = hoverId ? computeHighlightSets(hoverId, edgeList) : null;
334+
const hoverId = hoverRef.current?.node?.id || null;
335+
const fade = hoverFadeRef.current;
336+
if (hoverId && hoverId !== fade.id) {
337+
fade.id = hoverId;
338+
fade.alpha = 0;
339+
fade.lastTs = ts;
340+
}
341+
if (fade.lastTs == null) fade.lastTs = ts;
342+
const dt = Math.min(1, (ts - fade.lastTs) / 400);
343+
const target = hoverId ? 1 : 0;
344+
if (target > (fade.alpha || 0)) {
345+
fade.alpha = Math.min(1, (fade.alpha || 0) + dt);
346+
} else if (target < (fade.alpha || 0)) {
347+
fade.alpha = Math.max(0, (fade.alpha || 0) - dt);
348+
}
349+
fade.lastTs = ts;
350+
if (!hoverId && fade.alpha === 0) {
351+
fade.id = null;
352+
}
353+
const highlightId = fade.id;
354+
const highlightAlpha = fade.alpha || 0;
355+
const highlight = highlightId ? computeHighlightSets(highlightId, edgeList) : null;
323356
if (edgeList && edgeList.length) {
324357
ctx.strokeStyle = palette.link;
325358
ctx.lineWidth = 1.4;
@@ -332,13 +365,13 @@ export default function ProofSearchCanvas({
332365
if (!from || !to) continue;
333366
drawArrow(ctx, from.x, from.y, to.x, to.y);
334367
}
335-
if (highlight?.edgeKeys?.size) {
368+
if (highlight?.edgeKeys?.size && highlightAlpha > 0) {
336369
ctx.save();
337-
ctx.strokeStyle = palette.highlight;
370+
ctx.strokeStyle = '#c1121f';
338371
ctx.lineWidth = 3;
339-
ctx.globalAlpha = 0.5;
372+
ctx.globalAlpha = 0.55 * highlightAlpha;
340373
ctx.shadowColor = palette.highlight;
341-
ctx.shadowBlur = 16;
374+
ctx.shadowBlur = 14 * highlightAlpha;
342375
ctx.shadowOffsetX = 0;
343376
ctx.shadowOffsetY = 0;
344377
for (let i = 0; i < edgeList.length; i += step) {
@@ -351,7 +384,7 @@ export default function ProofSearchCanvas({
351384
}
352385
ctx.strokeStyle = palette.link;
353386
ctx.lineWidth = 1.6;
354-
ctx.globalAlpha = 0.95;
387+
ctx.globalAlpha = 0.95 * highlightAlpha;
355388
ctx.shadowBlur = 0;
356389
for (let i = 0; i < edgeList.length; i += step) {
357390
const edge = edgeList[i];
@@ -386,12 +419,12 @@ export default function ProofSearchCanvas({
386419
}
387420
}
388421

389-
if (highlight?.nodes?.has(String(node.id))) {
422+
if (highlight?.nodes?.has(String(node.id)) && highlightAlpha > 0) {
390423
const inner = radius * pulse + 2;
391424
const outer = radius * pulse + 20;
392425
const grad = ctx.createRadialGradient(node.x, node.y, inner, node.x, node.y, outer);
393-
grad.addColorStop(0, rgbaFrom(palette.highlight, 0.75));
394-
grad.addColorStop(0.5, rgbaFrom(palette.highlight, 0.35));
426+
grad.addColorStop(0, rgbaFrom(palette.highlight, 0.75 * highlightAlpha));
427+
grad.addColorStop(0.5, rgbaFrom(palette.highlight, 0.35 * highlightAlpha));
395428
grad.addColorStop(1, rgbaFrom(palette.highlight, 0));
396429
ctx.globalAlpha = 1;
397430
ctx.fillStyle = grad;
@@ -474,11 +507,20 @@ export default function ProofSearchCanvas({
474507
const screenX = node.x * zoom + panX;
475508
const screenY = node.y * zoom + panY;
476509
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
477-
const lines = wrapText(ctx, node.text || '', 220);
478510
const pad = 10;
479-
ctx.font = '12px system-ui, sans-serif';
511+
const maxWidth = Math.min(340, w - 20);
512+
ctx.font = '12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
480513
const lineHeight = 16;
481-
const boxWidth = 240;
514+
const tokens = tokenizeClause(node.text || '');
515+
const lines = wrapTokens(ctx, tokens, maxWidth - pad * 2);
516+
const lineWidths = lines.map((line) =>
517+
line.reduce((sum, tok) => sum + ctx.measureText(tok.text).width, 0)
518+
);
519+
const contentWidth = Math.min(
520+
maxWidth,
521+
Math.max(140, ...lineWidths.map((w) => w + pad * 2))
522+
);
523+
const boxWidth = Math.min(maxWidth, contentWidth);
482524
const boxHeight = pad * 2 + lineHeight * Math.max(1, lines.length);
483525
const boxX = clamp(screenX + 20, 10, w - boxWidth - 10);
484526
const boxY = clamp(screenY + 20, 10, h - boxHeight - 10);
@@ -491,11 +533,17 @@ export default function ProofSearchCanvas({
491533
ctx.fill();
492534
ctx.stroke();
493535

494-
ctx.fillStyle = palette.tooltipText;
495536
ctx.textAlign = 'left';
496537
ctx.textBaseline = 'top';
497538
lines.forEach((line, idx) => {
498-
ctx.fillText(line, boxX + pad, boxY + pad + idx * lineHeight);
539+
let cursorX = boxX + pad;
540+
const y = boxY + pad + idx * lineHeight;
541+
line.forEach((tok) => {
542+
if (!tok.text) return;
543+
ctx.fillStyle = tokenColor(tok.type, palette);
544+
ctx.fillText(tok.text, cursorX, y);
545+
cursorX += ctx.measureText(tok.text).width;
546+
});
499547
});
500548
}
501549

@@ -844,22 +892,83 @@ export default function ProofSearchCanvas({
844892
);
845893
}
846894

847-
function wrapText(ctx, text, maxWidth) {
848-
const words = String(text || '').split(/\s+/).filter(Boolean);
849-
if (!words.length) return ['(empty clause)'];
895+
function tokenizeClause(text) {
896+
const raw = String(text || '').trim();
897+
if (!raw) {
898+
return [{text: '(empty clause)', type: 'empty'}];
899+
}
900+
const regex = /(\s+|\$false|\$true|!=|<=|>=|<=>|=>|[()|,&=:.~]|[A-Za-z_][A-Za-z0-9_]*|\d+|[^\s])/g;
901+
const tokens = [];
902+
let match;
903+
while ((match = regex.exec(raw)) !== null) {
904+
const value = match[0];
905+
tokens.push({text: value, type: classifyToken(value)});
906+
}
907+
return tokens;
908+
}
909+
910+
function classifyToken(token) {
911+
if (/^\s+$/.test(token)) return 'ws';
912+
if (/^\$false$|^\$true$/i.test(token)) return 'bool';
913+
if (/^[A-Z]/.test(token)) return 'var';
914+
if (/^\d+$/.test(token)) return 'num';
915+
if (/^(~|\||&|=>|<=>|=|!=|<=|>=)$/.test(token)) return 'op';
916+
if (/^[a-z]/.test(token)) return 'sym';
917+
if (/^[()|,&=:.~]$/.test(token)) return 'punct';
918+
return 'sym';
919+
}
920+
921+
function tokenColor(type, palette) {
922+
switch (type) {
923+
case 'op':
924+
return palette.tooltipOp;
925+
case 'var':
926+
return palette.tooltipVar;
927+
case 'num':
928+
return palette.tooltipNum;
929+
case 'bool':
930+
return palette.tooltipBool;
931+
case 'punct':
932+
return palette.tooltipPunct;
933+
case 'ws':
934+
return palette.tooltipText;
935+
default:
936+
return palette.tooltipSym || palette.tooltipText;
937+
}
938+
}
939+
940+
function wrapTokens(ctx, tokens, maxWidth) {
850941
const lines = [];
851-
let line = words[0];
852-
for (let i = 1; i < words.length; i += 1) {
853-
const test = line + ' ' + words[i];
854-
if (ctx.measureText(test).width > maxWidth) {
855-
lines.push(line);
856-
line = words[i];
857-
} else {
858-
line = test;
942+
let line = [];
943+
let width = 0;
944+
const pushLine = () => {
945+
if (line.length) lines.push(line);
946+
line = [];
947+
width = 0;
948+
};
949+
tokens.forEach((tok) => {
950+
if (tok.type === 'ws' && line.length === 0) return;
951+
const tokenWidth = ctx.measureText(tok.text).width;
952+
if (tokenWidth > maxWidth && tok.text.length > 1) {
953+
// Break long tokens into characters
954+
tok.text.split('').forEach((ch) => {
955+
const chWidth = ctx.measureText(ch).width;
956+
if (line.length && width + chWidth > maxWidth) {
957+
pushLine();
958+
}
959+
line.push({text: ch, type: tok.type});
960+
width += chWidth;
961+
});
962+
return;
859963
}
860-
}
861-
lines.push(line);
862-
return lines.slice(0, 6);
964+
if (line.length && width + tokenWidth > maxWidth) {
965+
pushLine();
966+
}
967+
line.push(tok);
968+
width += tokenWidth;
969+
});
970+
if (line.length) lines.push(line);
971+
return lines.slice(0, 8);
863972
}
864973

865974
function drawArrow(ctx, x1, y1, x2, y2) {

0 commit comments

Comments
 (0)