[Bug] Coloring page and detail animations #5
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user