[Bug] Coloring page and detail animations #5

Merged
iamdoubz merged 2 commits from bug_coloring_animations into main 2026-06-18 09:25:05 -05:00
2 changed files with 12 additions and 8 deletions
+8 -5
View File
@@ -49,6 +49,7 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
const committedRef = useRef<ImageData | null>(null); // active layer before current stroke
const undoRef = useRef<{ layerId: number; data: ImageData }[]>([]);
const drawingRef = useRef<{ x: number; y: number }[] | null>(null);
const pressureRef = useRef(0.7);
const saveTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const [, force] = useState(0);
@@ -152,12 +153,14 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
if (committedRef.current) ctx.putImageData(committedRef.current, 0, 0);
const pts = drawingRef.current!;
if (!pts.length) return;
ctx.save(); // isolate this stroke's tool settings so they can't leak to the next stroke
strokeStyleFor(pressureRef.current);
ctx.lineCap = "round"; ctx.lineJoin = "round";
ctx.beginPath(); ctx.moveTo(pts[0].x, pts[0].y);
if (pts.length === 1) ctx.lineTo(pts[0].x + 0.1, pts[0].y + 0.1);
else for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
ctx.stroke();
ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over";
ctx.restore();
composite();
}
@@ -196,13 +199,13 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
pushUndo();
committedRef.current = active.canvas.getContext("2d")!.getImageData(0, 0, W, H);
drawingRef.current = [p];
strokeStyleFor(e.pressure && e.pressure > 0 ? e.pressure : 0.7);
pressureRef.current = e.pressure && e.pressure > 0 ? e.pressure : 0.7;
renderStroke();
}
function onMove(e: React.PointerEvent) {
if (!drawingRef.current) return;
drawingRef.current.push(pt(e));
strokeStyleFor(e.pressure && e.pressure > 0 ? e.pressure : 0.7);
pressureRef.current = e.pressure && e.pressure > 0 ? e.pressure : 0.7;
renderStroke();
}
function onUp() { if (!drawingRef.current) return; drawingRef.current = null; committedRef.current = null; force((n) => n + 1); scheduleSave(); }
@@ -264,7 +267,7 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
}
const swatch = (c: string, key: string) => (
<button key={key} onClick={() => { setColor(c); if (tool === "eraser" || tool === "fill") setTool("pen"); }} aria-label={`Color ${c}`}
<button key={key} onClick={() => setColor(c)} aria-label={`Color ${c}`}
style={{ width: 30, height: 30, borderRadius: "50%", background: c, border: color === c ? "3px solid var(--ink)" : "3px solid #fff", boxShadow: "0 0 0 1px var(--line)", cursor: "pointer" }} />
);
@@ -319,7 +322,7 @@ export default function ColoringRunner({ meta, loggedIn, username, alreadyEarned
<span style={{ width: 1, height: 28, background: "var(--line)" }} />
<label title="Create a color" style={{ display: "inline-flex", alignItems: "center", gap: 6, cursor: "pointer", fontSize: "0.82rem", fontWeight: 700 }} className="muted">
New color
<input type="color" value={custom} onChange={(e) => { setCustom(e.target.value); setColor(e.target.value); if (tool === "eraser" || tool === "fill") setTool("pen"); }} style={{ width: 30, height: 30, border: "none", background: "none", cursor: "pointer" }} />
<input type="color" value={custom} onChange={(e) => { setCustom(e.target.value); setColor(e.target.value); }} style={{ width: 30, height: 30, border: "none", background: "none", cursor: "pointer" }} />
</label>
</div>
</div>
+4 -3
View File
@@ -24,8 +24,9 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed
const router = useRouter();
const lastIndex = steps.length - 1;
const [idx, setIdx] = useState(Math.min(completedSteps.length, lastIndex));
const [finished, setFinished] = useState(alreadyEarned);
const [earned, setEarned] = useState(alreadyEarned);
// Always open the lesson (so you can redraw/continue); show the finished screen only after finishing here.
const [finished, setFinished] = useState(false);
const [earned, setEarned] = useState(false);
const [playKey, setPlayKey] = useState(0);
const [playing, setPlaying] = useState(true);
const [finalImage, setFinalImage] = useState<string | null>(null);
@@ -71,7 +72,7 @@ export default function TraceRunner({ meta, steps, loggedIn, username, completed
useEffect(() => { if (!finished) resize(); }, [finished, resize]);
useEffect(() => {
if (!loggedIn || alreadyEarned) return; let alive = true;
if (!loggedIn) return; let alive = true;
loadProgressDrawing(meta.level, meta.sublevel).then((img) => {
// Use this lesson's own saved canvas if any; otherwise carry over the previous lesson's drawing
const src = img || carryImage;