this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: crossfade between question/answer images in review

Both images stacked in a CSS grid cell; opacity transitions on click.
Answer fades in on reveal, click toggles back to obscured view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+48 -17
+12 -1
crates/tala/assets/main.css
··· 356 356 font-family: 'JetBrains Mono', 'Fira Code', monospace; 357 357 } 358 358 359 - .review-card-img { 359 + .review-card-stack { 360 + display: grid; 361 + cursor: default; 362 + border-radius: 4px; 363 + max-width: 100%; 364 + } 365 + 366 + .review-card-stack.clickable { cursor: pointer; } 367 + 368 + .review-card-stack > img { 369 + grid-area: 1 / 1; 360 370 max-width: 100%; 361 371 max-height: 40vh; 362 372 object-fit: contain; 363 373 border-radius: 4px; 374 + transition: opacity 0.18s ease; 364 375 } 365 376 366 377 .review-divider {
+36 -16
crates/tala/src/main.rs
··· 2268 2268 mut session: Signal<ReviewSession>, 2269 2269 mut idx: Signal<usize>, 2270 2270 mut revealed: Signal<bool>, 2271 + mut show_answer: Signal<bool>, 2271 2272 mut done: Signal<bool>, 2272 2273 mut graded_count: Signal<usize>, 2273 2274 ) { ··· 2296 2297 } else { 2297 2298 idx.set(next); 2298 2299 revealed.set(false); 2300 + show_answer.set(false); 2299 2301 } 2300 2302 } 2301 2303 ··· 2307 2309 }); 2308 2310 let idx = use_signal(|| 0usize); 2309 2311 let mut revealed = use_signal(|| false); 2312 + let mut show_answer = use_signal(|| false); 2310 2313 let done = use_signal(|| false); 2311 2314 let graded_count = use_signal(|| 0usize); 2312 2315 ··· 2343 2346 .unwrap_or_default() 2344 2347 }); 2345 2348 2346 - // Answer-side image: re-renders when revealed or idx changes. 2349 + // Answer-side image: starts loading once revealed; cached until idx changes. 2347 2350 let a_img = use_resource(move || async move { 2348 2351 if !*revealed.read() { 2349 2352 return None; ··· 2377 2380 2378 2381 let cur_idx = *idx.read(); 2379 2382 let is_revealed = *revealed.read(); 2383 + let is_show_answer = *show_answer.read(); 2380 2384 let q_src = q_img.read(); 2381 2385 let a_src = a_img.read(); 2386 + let has_answer = matches!(a_src.as_ref(), Some(Some(_))); 2382 2387 2383 2388 rsx! { 2384 2389 div { ··· 2391 2396 " " | "Enter" => { 2392 2397 if !*revealed.read() { 2393 2398 revealed.set(true); 2399 + show_answer.set(true); 2394 2400 } 2395 2401 } 2396 2402 "1" => { 2397 2403 if *revealed.read() { 2398 - do_advance(Grade::Again, session, idx, revealed, done, graded_count); 2404 + do_advance(Grade::Again, session, idx, revealed, show_answer, done, graded_count); 2399 2405 } 2400 2406 } 2401 2407 "2" => { 2402 2408 if *revealed.read() { 2403 - do_advance(Grade::Hard, session, idx, revealed, done, graded_count); 2409 + do_advance(Grade::Hard, session, idx, revealed, show_answer, done, graded_count); 2404 2410 } 2405 2411 } 2406 2412 "3" => { 2407 2413 if *revealed.read() { 2408 - do_advance(Grade::Good, session, idx, revealed, done, graded_count); 2414 + do_advance(Grade::Good, session, idx, revealed, show_answer, done, graded_count); 2409 2415 } 2410 2416 } 2411 2417 "4" => { 2412 2418 if *revealed.read() { 2413 - do_advance(Grade::Easy, session, idx, revealed, done, graded_count); 2419 + do_advance(Grade::Easy, session, idx, revealed, show_answer, done, graded_count); 2414 2420 } 2415 2421 } 2416 2422 _ => {} 2417 2423 }, 2418 2424 span { class: "review-progress", "{cur_idx + 1} / {total}" } 2419 - if let Some(src) = q_src.as_ref().filter(|s| !s.is_empty()) { 2420 - img { class: "review-card-img", src: "{src}" } 2425 + if let Some(q) = q_src.as_ref().filter(|s| !s.is_empty()) { 2426 + div { 2427 + class: if has_answer { "review-card-stack clickable" } else { "review-card-stack" }, 2428 + onclick: move |_| { 2429 + if has_answer { 2430 + let cur = *show_answer.read(); 2431 + show_answer.set(!cur); 2432 + } 2433 + }, 2434 + img { 2435 + src: "{q}", 2436 + style: if is_revealed && is_show_answer { "opacity: 0" } else { "opacity: 1" }, 2437 + } 2438 + if let Some(Some(a)) = a_src.as_ref() { 2439 + img { 2440 + src: "{a}", 2441 + style: if is_show_answer { "opacity: 1" } else { "opacity: 0" }, 2442 + } 2443 + } 2444 + } 2421 2445 } else { 2422 2446 span { class: "status", "Rendering..." } 2423 2447 } 2424 2448 if is_revealed { 2425 - div { class: "review-divider" } 2426 - if let Some(Some(src)) = a_src.as_ref() { 2427 - img { class: "review-card-img", src: "{src}" } 2428 - } 2429 2449 div { class: "review-grade-row", 2430 2450 button { 2431 2451 class: "btn grade-again", 2432 - onclick: move |_| do_advance(Grade::Again, session, idx, revealed, done, graded_count), 2452 + onclick: move |_| do_advance(Grade::Again, session, idx, revealed, show_answer, done, graded_count), 2433 2453 "1 Again" 2434 2454 } 2435 2455 button { 2436 2456 class: "btn grade-hard", 2437 - onclick: move |_| do_advance(Grade::Hard, session, idx, revealed, done, graded_count), 2457 + onclick: move |_| do_advance(Grade::Hard, session, idx, revealed, show_answer, done, graded_count), 2438 2458 "2 Hard" 2439 2459 } 2440 2460 button { 2441 2461 class: "btn grade-good", 2442 - onclick: move |_| do_advance(Grade::Good, session, idx, revealed, done, graded_count), 2462 + onclick: move |_| do_advance(Grade::Good, session, idx, revealed, show_answer, done, graded_count), 2443 2463 "3 Good" 2444 2464 } 2445 2465 button { 2446 2466 class: "btn grade-easy", 2447 - onclick: move |_| do_advance(Grade::Easy, session, idx, revealed, done, graded_count), 2467 + onclick: move |_| do_advance(Grade::Easy, session, idx, revealed, show_answer, done, graded_count), 2448 2468 "4 Easy" 2449 2469 } 2450 2470 } 2451 2471 } else { 2452 2472 button { 2453 2473 class: "btn", 2454 - onclick: move |_| revealed.set(true), 2474 + onclick: move |_| { revealed.set(true); show_answer.set(true); }, 2455 2475 "Show answer [Space]" 2456 2476 } 2457 2477 }